diff --git a/changelog.txt b/changelog.txt index bf2ea97f0..98e1f0606 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,70 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 08/26/2014 == +Uleat: Implemented 'Smart' Player Trade transfers. Trades are processed by containers, stackables and then all remaining. QueryServ logs have been updated to match these transactions. +Note: QueryServ logs previously listed 'Items' on the main entry table. This indicated the number of slots affected and not the actual number of items. +This field now indicates the actual number of items transferred. For non-stackable, the value is '1' and stackable is the number of charges. A _detail_count +property has been added to both 'Trade' and 'Handin' structs to indicate the number of details recorded..though, not tracked..it could be added. + +== 08/24/2014 == +Uleat: Fix (attempted) for zone crashes related to zone shut-down. This change disables all Mob AI and disables/deletes all Mob timers once Zone::ShutDown() is called. More areas will be addressed as reports come in. +Note: Perl and Lua quests tested to work..please post any aberrant behavior. (I finally set my spell-check to US English...) +Akkadius: Character creation process crash fix and query cleanup +Akkadius: Created `character_lookup` table for applications that mirrors all `character_` table fields minus blob fields for application lookups + - A 2.4GB character_ table will take 7 seconds to query on a SSD versus .1s on the character_lookup table + - This also causes applications like Magelo to burst reads of the entire character table because of the blob fields that come with the reads, as much as 500-600MB/s even if a indexed id filter is provided + - This field is synchronized on player save and has 0.001s DB hit + - When we split out from the blob, ideally this table can be removed + - Required SQL: utils\sql\git\required\2014_08_24_character_lookup.sql + +== 08/23/2014 == +Akkadius: Changed zone process window title format, example: 'crushbone :: clients: 6 inst_id: 1 inst_ver: 0 :: port: 7015' +Akkadius: Most of the following changes are QueryServ related, fully implemented its original functionality to be able to offload + intensive or metric based logging to a remote server process that could exist on another server entirely +Akkadius: Implemented Player Event Logging Types (Go to table `qs_player_events`): + 1 = Player_Log_Quest, + 2 = Player_Log_Zoning, + 3 = Player_Log_Deaths, + 4 = Player_Log_Connect_State, + 5 = Player_Log_Levels, + 6 = Player_Log_Keyring_Addition, + 7 = Player_Log_QGlobal_Update, + 8 = Player_Log_Task_Updates, + 9 = Player_Log_AA_Purchases, + 10 = Player_Log_Trade_Skill_Events, + 11 = Player_Log_Issued_Commands, + 12 = Player_Log_Money_Transactions, + 13 = Player_Log_Alternate_Currency_Transactions, + - All QueryServ logging will be implemented with a front end in EoC 2.0 very soon + - Architecture page: http://wiki.eqemulator.org/p?QueryServ_Architecture +Akkadius: Changed all QS Error related logging to 'QUERYSERV__ERROR' +Akkadius: (Natedog) (Crash Fix) Legacy MySQL bug revert for loading AA's COALESCE( from COALESCE ( +Akkadius: Implemented Perl Quest objects (LUA still needed to be exported): + - quest::qs_send_query("MySQL query") - Will send a raw query to the QueryServ process, useful for custom logging + - quest::qs_player_event(char_id, event_desc); - Will process a quest type event to table `qs_player_events` +Akkadius: Added MySQL Tables + - `qs_player_aa_rate_hourly` + - `qs_player_events` + - Source table structures from: + - utils\sql\git\queryserv\required\08_23_2014_player_events_and_player_aa_rate_hourly + To get the complete QueryServ schema, source from here: + - utils\sql\git\queryserv\required\Complete_QueryServ_Table_Structures.sql +Akkadius: Added rules for each logging type, source rules here with them enabled by default: + - utils\sql\git\queryserv\required\Complete_QueryServ_Rules_Enabled.sql +Akkadius: Spawn related logging cleanup +Akkadius: General code cleanup +Akkadius: More to come for QueryServ + +== 08/22/2014 == +Uleat: Rework of Trade::FinishedTrade() and Trade::ResetTrade() to parse items a little more intelligently. + +Trade window items are now sent to client inventory in this order: + - Bags + - Partial stack movements + - All remaining items + +If any of these procedures cause any problems, please post them immediately. + == 08/20/2014 == Uleat: Rework of Trade::AddEntity() - function used to move items into the trade window. Now accepts argument for 'stack_size' and updates client properly. Note: I tested trade with Titanium:{SoF,SoD,UF,RoF} in both directions and no client generated an OP_MoveItem event for attempting to place a stackable diff --git a/client_files/export/main.cpp b/client_files/export/main.cpp index c4faa4718..e9b127aad 100644 --- a/client_files/export/main.cpp +++ b/client_files/export/main.cpp @@ -81,7 +81,9 @@ void ExportSpells(SharedDatabase *db) { line.push_back('^'); } - line += row[i]; + if(row[i] != nullptr) { + line += row[i]; + } } fprintf(f, "%s\n", line.c_str()); @@ -180,7 +182,9 @@ void ExportBaseData(SharedDatabase *db) { if(rowIndex != 0) line.push_back('^'); - line += row[rowIndex]; + if(row[rowIndex] != nullptr) { + line += row[rowIndex]; + } } fprintf(f, "%s\n", line.c_str()); diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index f02212161..a6a25f612 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -93,6 +93,7 @@ SET(common_sources ) SET(common_headers + any.h base_packet.h base_data.h bodytypes.h diff --git a/common/any.h b/common/any.h new file mode 100644 index 000000000..fd80fbc5f --- /dev/null +++ b/common/any.h @@ -0,0 +1,190 @@ +/* + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +// EQEmu::Any is a modified version of Boost::Any and as such retains the Boost licensing. + +#ifndef EQEMU_COMMON_ANY_H +#define EQEMU_COMMON_ANY_H + +#include +#include + +namespace EQEmu +{ + class Any + { + public: + Any() + : content(nullptr) + { + } + + template + Any(const ValueType &value) + : content(new Holder(value)) + { + } + + Any(const Any &other) + : content(other.content ? other.content->clone() : 0) + { + } + + ~Any() + { + if(content) + delete content; + } + + Any& swap(Any &rhs) + { + std::swap(content, rhs.content); + return *this; + } + + template + Any& operator=(const ValueType &rhs) + { + Any(rhs).swap(*this); + return *this; + } + + Any& operator=(Any rhs) + { + rhs.swap(*this); + return *this; + } + + bool empty() const + { + return !content; + } + + const std::type_info& type() const + { + return content ? content->type() : typeid(void); + } + + class Placeholder + { + public: + virtual ~Placeholder() + { + } + + virtual const std::type_info& type() const = 0; + virtual Placeholder* clone() const = 0; + }; + + + template + class Holder : public Placeholder + { + public: + Holder(const ValueType &value) + : held(value) + { + } + + virtual const std::type_info& type() const + { + return typeid(ValueType); + } + + virtual Placeholder* clone() const + { + return new Holder(held); + } + + ValueType held; + + private: + Holder& operator=(const Holder&); + }; + + private: + template + friend ValueType* any_cast(Any*); + + template + friend ValueType* unsafe_any_cast(Any*); + + Placeholder* content; + }; + + class bad_any_cast : public std::bad_cast + { + public: + virtual const char * what() const throw() + { + return "DBI::bad_any_cast: failed conversion using DBI::any_cast"; + } + }; + + template + ValueType* any_cast(Any* operand) + { + return operand && + operand->type() == typeid(ValueType) ? &static_cast*>(operand->content)->held : nullptr; + } + + template + inline const ValueType* any_cast(const Any* operand) + { + return any_cast(const_cast(operand)); + } + + template + ValueType any_cast(Any& operand) + { + typedef typename std::remove_reference::type nonref; + nonref* result = any_cast(&operand); + if(!result) + throw bad_any_cast(); + return *result; + } + + template + inline ValueType any_cast(const Any& operand) + { + typedef typename std::remove_reference::type nonref; + return any_cast(const_cast(operand)); + } + + template + inline ValueType* unsafe_any_cast(Any* operand) + { + return &static_cast*>(operand->content)->held; + } + + template + inline const ValueType* unsafe_any_cast(const Any* operand) + { + return unsafe_any_cast(const_cast(operand)); + } +} + +#endif diff --git a/common/database.cpp b/common/database.cpp index e82f350b1..b99eac2ea 100644 --- a/common/database.cpp +++ b/common/database.cpp @@ -161,6 +161,9 @@ uint32 Database::CheckLogin(const char* name, const char* password, int16* oStat return 0; } + if(results.RowCount() == 0) + return 0; + auto row = results.begin(); uint32 id = atoi(row[0]); @@ -608,10 +611,11 @@ bool Database::StoreCharacter(uint32 account_id, PlayerProfile_Struct* pp, Inven for (int16 i=EmuConstants::EQUIPMENT_BEGIN; i<=EmuConstants::BANK_BAGS_END;) { const ItemInst* newinv = inv->GetItem(i); - if (!newinv) + if (newinv) { - invquery = StringFormat("INSERT INTO inventory SET charid=%0u, slotid=%0d, itemid=%0u, charges=%0d, color=%0u", - charid, i, newinv->GetItem()->ID,newinv->GetCharges(), newinv->GetColor()); + invquery = StringFormat("INSERT INTO `inventory` (charid, slotid, itemid, charges, color) VALUES (%u, %i, %u, %i, %u)", + charid, i, newinv->GetItem()->ID, newinv->GetCharges(), newinv->GetColor()); + auto results = QueryDatabase(invquery); if (!results.RowsAffected()) diff --git a/common/item.cpp b/common/item.cpp index 7d6618335..bbb6f14f8 100644 --- a/common/item.cpp +++ b/common/item.cpp @@ -654,6 +654,99 @@ int16 Inventory::FindFreeSlot(bool for_bag, bool try_cursor, uint8 min_size, boo return INVALID_INDEX; } +// This is a mix of HasSpaceForItem and FindFreeSlot..due to existing coding behavior, it was better to add a new helper function... +int16 Inventory::FindFreeSlotForTradeItem(const ItemInst* inst) { + // Do not arbitrarily use this function..it is designed for use with Client::ResetTrade() and Client::FinishTrade(). + // If you have a need, use it..but, understand it is not a compatible replacement for Inventory::FindFreeSlot(). + // + // I'll probably implement a bitmask in the new inventory system to avoid having to adjust stack bias -U + + if (!inst || !inst->GetID()) + return INVALID_INDEX; + + // step 1: find room for bags (caller should really ask for slots for bags first to avoid sending them to cursor..and bag item loss) + if (inst->IsType(ItemClassContainer)) { + for (int16 free_slot = EmuConstants::GENERAL_BEGIN; free_slot <= EmuConstants::GENERAL_END; ++free_slot) + if (!m_inv[free_slot]) + return free_slot; + + return MainCursor; // return cursor since bags do not stack and will not fit inside other bags..yet...) + } + + // step 2: find partial room for stackables + if (inst->IsStackable()) { + for (int16 free_slot = EmuConstants::GENERAL_BEGIN; free_slot <= EmuConstants::GENERAL_END; ++free_slot) { + const ItemInst* main_inst = m_inv[free_slot]; + + if (!main_inst) + continue; + + if ((main_inst->GetID() == inst->GetID()) && (main_inst->GetCharges() < main_inst->GetItem()->StackSize)) + return free_slot; + + if (main_inst->IsType(ItemClassContainer)) { // if item-specific containers already have bad items, we won't fix it here... + for (uint8 free_bag_slot = SUB_BEGIN; (free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot < EmuConstants::ITEM_CONTAINER_SIZE); ++free_bag_slot) { + const ItemInst* sub_inst = main_inst->GetItem(free_bag_slot); + + if (!sub_inst) + continue; + + if ((sub_inst->GetID() == inst->GetID()) && (sub_inst->GetCharges() < sub_inst->GetItem()->StackSize)) + return Inventory::CalcSlotId(free_slot, free_bag_slot); + } + } + } + } + + // step 3a: find room for container-specific items (ItemClassArrow) + if (inst->GetItem()->ItemType == ItemTypeArrow) { + for (int16 free_slot = EmuConstants::GENERAL_BEGIN; free_slot <= EmuConstants::GENERAL_END; ++free_slot) { + const ItemInst* main_inst = m_inv[free_slot]; + + if (!main_inst || (main_inst->GetItem()->BagType != BagTypeQuiver) || !main_inst->IsType(ItemClassContainer)) + continue; + + for (uint8 free_bag_slot = SUB_BEGIN; (free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot < EmuConstants::ITEM_CONTAINER_SIZE); ++free_bag_slot) + if (!main_inst->GetItem(free_bag_slot)) + return Inventory::CalcSlotId(free_slot, free_bag_slot); + } + } + + // step 3b: find room for container-specific items (ItemClassSmallThrowing) + if (inst->GetItem()->ItemType == ItemTypeSmallThrowing) { + for (int16 free_slot = EmuConstants::GENERAL_BEGIN; free_slot <= EmuConstants::GENERAL_END; ++free_slot) { + const ItemInst* main_inst = m_inv[free_slot]; + + if (!main_inst || (main_inst->GetItem()->BagType != BagTypeBandolier) || !main_inst->IsType(ItemClassContainer)) + continue; + + for (uint8 free_bag_slot = SUB_BEGIN; (free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot < EmuConstants::ITEM_CONTAINER_SIZE); ++free_bag_slot) + if (!main_inst->GetItem(free_bag_slot)) + return Inventory::CalcSlotId(free_slot, free_bag_slot); + } + } + + // step 4: just find an empty slot + for (int16 free_slot = EmuConstants::GENERAL_BEGIN; free_slot <= EmuConstants::GENERAL_END; ++free_slot) { + const ItemInst* main_inst = m_inv[free_slot]; + + if (!main_inst) + return free_slot; + + if (main_inst->IsType(ItemClassContainer)) { + if ((main_inst->GetItem()->BagSize < inst->GetItem()->Size) || (main_inst->GetItem()->BagType == BagTypeBandolier) || (main_inst->GetItem()->BagType == BagTypeQuiver)) + continue; + + for (uint8 free_bag_slot = SUB_BEGIN; (free_bag_slot < main_inst->GetItem()->BagSlots) && (free_bag_slot < EmuConstants::ITEM_CONTAINER_SIZE); ++free_bag_slot) + if (!main_inst->GetItem(free_bag_slot)) + return Inventory::CalcSlotId(free_slot, free_bag_slot); + } + } + + //return INVALID_INDEX; // everything else pushes to the cursor + return MainCursor; +} + // Opposite of below: Get parent bag slot_id from a slot inside of bag int16 Inventory::CalcSlotId(int16 slot_id) { int16 parent_slot_id = INVALID_INDEX; diff --git a/common/item.h b/common/item.h index ef59ddd6f..f279bd2aa 100644 --- a/common/item.h +++ b/common/item.h @@ -172,6 +172,7 @@ public: // Locate an available inventory slot int16 FindFreeSlot(bool for_bag, bool try_cursor, uint8 min_size = 0, bool is_arrow = false); + int16 FindFreeSlotForTradeItem(const ItemInst* inst); // Calculate slot_id for an item within a bag static int16 CalcSlotId(int16 slot_id); // Calc parent bag's slot_id diff --git a/common/logtypes.h b/common/logtypes.h index 1776c9518..8ebd48f9a 100644 --- a/common/logtypes.h +++ b/common/logtypes.h @@ -60,17 +60,17 @@ LOG_TYPE( UCS, PACKETS, DISABLED) LOG_CATEGORY( QUERYSERV ) LOG_TYPE( QUERYSERV, INIT, ENABLED ) -LOG_TYPE( QUERYSERV, ERROR, ENABLED ) +LOG_TYPE( QUERYSERV, ERROR, ENABLED ) LOG_TYPE( QUERYSERV, CLIENT, DISABLED ) LOG_TYPE( QUERYSERV, TRACE, DISABLED ) LOG_TYPE( QUERYSERV, PACKETS, DISABLED) -LOG_CATEGORY(SOCKET_SERVER) -LOG_TYPE(SOCKET_SERVER, INIT, ENABLED) -LOG_TYPE(SOCKET_SERVER, ERROR, ENABLED) -LOG_TYPE(SOCKET_SERVER, CLIENT, DISABLED) -LOG_TYPE(SOCKET_SERVER, TRACE, DISABLED) -LOG_TYPE(SOCKET_SERVER, PACKETS, DISABLED) +LOG_CATEGORY( SOCKET_SERVER) +LOG_TYPE( SOCKET_SERVER, INIT, ENABLED) +LOG_TYPE( SOCKET_SERVER, ERROR, ENABLED) +LOG_TYPE( SOCKET_SERVER, CLIENT, DISABLED) +LOG_TYPE( SOCKET_SERVER, TRACE, DISABLED) +LOG_TYPE( SOCKET_SERVER, PACKETS, DISABLED) LOG_CATEGORY( SPAWNS ) LOG_TYPE( SPAWNS, MAIN, DISABLED ) @@ -108,6 +108,7 @@ LOG_CATEGORY( FACTION ) LOG_CATEGORY( ZONE ) LOG_TYPE( ZONE, GROUND_SPAWNS, DISABLED ) +LOG_TYPE( ZONE, SPAWNS, ENABLED) LOG_TYPE( ZONE, INIT, ENABLED ) LOG_TYPE( ZONE, INIT_ERR, ENABLED ) LOG_TYPE( ZONE, WORLD, ENABLED ) @@ -116,7 +117,7 @@ LOG_TYPE( ZONE, WORLD_TRACE, DISABLED ) LOG_CATEGORY( TASKS ) LOG_TYPE( TASKS, GLOBALLOAD, DISABLED ) -LOG_TYPE( TASKS, CLIENTLOAD, DISABLED ) +LOG_TYPE( TASKS, CLIENTLOAD, DISABLED ) LOG_TYPE( TASKS, UPDATE, DISABLED ) LOG_TYPE( TASKS, CLIENTSAVE, DISABLED ) LOG_TYPE( TASKS, PACKETS, DISABLED ) diff --git a/common/mysql_request_result.h b/common/mysql_request_result.h index cd561be56..ab84eeb18 100644 --- a/common/mysql_request_result.h +++ b/common/mysql_request_result.h @@ -40,7 +40,7 @@ public: MySQLRequestResult& operator=(MySQLRequestResult&& other); bool Success() const { return m_Success;} - std::string ErrorMessage() const {return std::string(m_ErrorBuffer);} + std::string ErrorMessage() const {return m_ErrorBuffer ? std::string(m_ErrorBuffer) : std::string("");} uint32 ErrorNumber() const {return m_ErrorNumber;} uint32 RowsAffected() const {return m_RowsAffected;} uint32 RowCount() const {return m_RowCount;} diff --git a/common/mysql_request_row.cpp b/common/mysql_request_row.cpp index ac5fa1592..9491fd1b2 100644 --- a/common/mysql_request_row.cpp +++ b/common/mysql_request_row.cpp @@ -64,6 +64,5 @@ bool MySQLRequestRow::operator!=(const MySQLRequestRow& rhs) char* MySQLRequestRow::operator[](int index) { - return m_MySQLRow[index]; } diff --git a/common/ruletypes.h b/common/ruletypes.h index 91ade257b..73d538f8a 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -558,14 +558,28 @@ RULE_INT ( Console, SessionTimeOut, 600000 ) // Amount of time in ms for the con RULE_CATEGORY_END() RULE_CATEGORY( QueryServ ) -RULE_BOOL( QueryServ, PlayerChatLogging, false) // Logs Player Chat +RULE_BOOL( QueryServ, PlayerLogChat, false) // Logs Player Chat RULE_BOOL( QueryServ, PlayerLogTrades, false) // Logs Player Trades RULE_BOOL( QueryServ, PlayerLogHandins, false) // Logs Player Handins RULE_BOOL( QueryServ, PlayerLogNPCKills, false) // Logs Player NPC Kills RULE_BOOL( QueryServ, PlayerLogDeletes, false) // Logs Player Deletes RULE_BOOL( QueryServ, PlayerLogMoves, false) // Logs Player Moves -RULE_BOOL( QueryServ, MerchantLogTransactions, false) // Logs Merchant Transactions +RULE_BOOL( QueryServ, PlayerLogMerchantTransactions, false) // Logs Merchant Transactions RULE_BOOL( QueryServ, PlayerLogPCCoordinates, false) // Logs Player Coordinates with certain events +RULE_BOOL( QueryServ, PlayerLogDropItem, false) // Logs Player Drop Item +RULE_BOOL( QueryServ, PlayerLogZone, false) // Logs Player Zone Events +RULE_BOOL( QueryServ, PlayerLogDeaths, false) // Logs Player Deaths +RULE_BOOL( QueryServ, PlayerLogConnectDisconnect, false) // Logs Player Connect Disconnect State +RULE_BOOL( QueryServ, PlayerLogLevels, false) // Logs Player Leveling/Deleveling +RULE_BOOL( QueryServ, PlayerLogAARate, false) // Logs Player AA Experience Rates +RULE_BOOL( QueryServ, PlayerLogQGlobalUpdate, false) // Logs Player QGlobal Updates +RULE_BOOL( QueryServ, PlayerLogTaskUpdates, false) // Logs Player Task Updates +RULE_BOOL( QueryServ, PlayerLogKeyringAddition, false) // Log PLayer Keyring additions +RULE_BOOL( QueryServ, PlayerLogAAPurchases, false) // Log Player AA Purchases +RULE_BOOL( QueryServ, PlayerLogTradeSkillEvents, false) // Log Player Tradeskill Transactions +RULE_BOOL( QueryServ, PlayerLogIssuedCommandes, false ) // Log Player Issued Commands +RULE_BOOL( QueryServ, PlayerLogMoneyTransactions, false) // Log Player Money Transaction/Splits +RULE_BOOL( QueryServ, PlayerLogAlternateCurrencyTransactions, false) // Log Ploayer Alternate Currency Transactions RULE_CATEGORY_END() RULE_CATEGORY( Inventory ) diff --git a/common/servertalk.h b/common/servertalk.h index d5d7c3604..5337b6b1d 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -184,9 +184,11 @@ #define ServerOP_QSPlayerLogNPCKills 0x4012 #define ServerOP_QSPlayerLogDeletes 0x4013 #define ServerOP_QSPlayerLogMoves 0x4014 -#define ServerOP_QSMerchantLogTransactions 0x4015 +#define ServerOP_QSPlayerLogMerchantTransactions 0x4015 +#define ServerOP_QSSendQuery 0x4016 -enum { QSG_LFGuild = 0 }; +/* Query Serv Generic Packet Flag/Type Enumeration */ +enum { QSG_LFGuild = 0 }; enum { QSG_LFGuild_PlayerMatches = 0, QSG_LFGuild_UpdatePlayerInfo, QSG_LFGuild_RequestPlayerInfo, QSG_LFGuild_UpdateGuildInfo, QSG_LFGuild_GuildMatches, QSG_LFGuild_RequestGuildInfo }; @@ -1116,6 +1118,7 @@ struct QSPlayerLogTrade_Struct { uint32 char2_id; MoneyUpdate_Struct char2_money; uint16 char2_count; + uint16 _detail_count; QSTradeItems_Struct items[0]; }; @@ -1139,6 +1142,7 @@ struct QSPlayerLogHandin_Struct { uint32 npc_id; MoneyUpdate_Struct npc_money; uint16 npc_count; + uint16 _detail_count; QSHandinItems_Struct items[0]; }; @@ -1219,6 +1223,10 @@ struct QSMerchantLogTransaction_Struct { QSTransactionItems_Struct items[0]; }; +struct QSGeneralQuery_Struct { + char QueryString[0]; +}; + struct CZMessagePlayer_Struct { uint32 Type; char CharName[64]; diff --git a/common/shareddb.cpp b/common/shareddb.cpp index 8e999da54..58a75a261 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -1632,6 +1632,13 @@ void SharedDatabase::LoadSpells(void *data, int max_spells) { int tempid = 0; int counter = 0; + + if(result && mysql_field_count(getMySQL()) <= SPELL_LOAD_FIELD_COUNT) { + _log(SPELLS__LOAD_ERR, "Fatal error loading spells: Spell field count < SPELL_LOAD_FIELD_COUNT(%u)", SPELL_LOAD_FIELD_COUNT); + mysql_free_result(result); + return; + } + while (row = mysql_fetch_row(result)) { tempid = atoi(row[0]); if(tempid >= max_spells) { diff --git a/common/spdat.h b/common/spdat.h index 40f5e658f..2dfc016b4 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -621,6 +621,8 @@ typedef enum { // number. note that the id field is counted as 0, this way the numbers // here match the numbers given to sep in the loading function net.cpp // +#define SPELL_LOAD_FIELD_COUNT 231 + struct SPDat_Spell_Struct { /* 000 */ int id; // not used diff --git a/common/string_util.cpp b/common/string_util.cpp index 0ccd0e4ef..2fcb59e05 100644 --- a/common/string_util.cpp +++ b/common/string_util.cpp @@ -53,10 +53,11 @@ const std::string vStringFormat(const char* format, va_list args) return ""; } else if ((unsigned int)characters_used > output.capacity()) { - output.resize(characters_used+1); + 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); if (characters_used < 0) { // We shouldn't have a format error by this point, but I can't imagine what error we @@ -72,6 +73,8 @@ const std::string vStringFormat(const char* format, va_list args) characters_used = vsnprintf(&output[0], output.capacity(), format, tmpargs); va_end(tmpargs); + output.resize(characters_used); + if (characters_used < 0) { // 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; @@ -380,6 +383,42 @@ std::string EscapeString(const std::string &s) { 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 isAlphaNumeric(const char *text) { for (unsigned int charIndex=0; charIndex #include #include -#include +#include #include #include #include #include #include +#include // Disgrace: for windows compile #ifdef _WINDOWS @@ -95,42 +96,7 @@ Close the connection to the database Database::~Database() { } - -bool Database::GetVariable(const char* varname, char* varvalue, uint16 varvalue_len) { - - char errbuf[MYSQL_ERRMSG_SIZE]; - char* query = 0; - MYSQL_RES *result; - MYSQL_ROW row; - - if (!RunQuery(query,MakeAnyLenString(&query, "select `value` from `variables` where `varname`='%s'", varname), errbuf, &result)) { - - _log(UCS__ERROR, "Unable to get message count from database. %s %s", query, errbuf); - - safe_delete_array(query); - - return false; - } - - safe_delete_array(query); - - if (mysql_num_rows(result) != 1) { - - mysql_free_result(result); - - return false; - } - - row = mysql_fetch_row(result); - - snprintf(varvalue, varvalue_len, "%s", row[0]); - - mysql_free_result(result); - - return true; -} - - + void Database::AddSpeech(const char* from, const char* to, const char* message, uint16 minstatus, uint32 guilddbid, uint8 type) { char errbuf[MYSQL_ERRMSG_SIZE]; char* query = 0; @@ -143,8 +109,8 @@ void Database::AddSpeech(const char* from, const char* to, const char* message, DoEscapeString(S3, message, strlen(message)); if(!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO `qs_player_speech` SET `from`='%s', `to`='%s', `message`='%s', `minstatus`='%i', `guilddbid`='%i', `type`='%i'", S1, S2, S3, minstatus, guilddbid, type), errbuf, 0, 0)) { - _log(NET__WORLD, "Failed Speech Entry Insert: %s", errbuf); - _log(NET__WORLD, "%s", query); + _log(QUERYSERV__ERROR, "Failed Speech Entry Insert: %s", errbuf); + _log(QUERYSERV__ERROR, "%s", query); } safe_delete_array(query); @@ -153,7 +119,7 @@ void Database::AddSpeech(const char* from, const char* to, const char* message, safe_delete_array(S3); } -void Database::LogPlayerTrade(QSPlayerLogTrade_Struct* QS, uint32 Items) { +void Database::LogPlayerTrade(QSPlayerLogTrade_Struct* QS, uint32 DetailCount) { char errbuf[MYSQL_ERRMSG_SIZE]; char* query = 0; @@ -164,27 +130,26 @@ void Database::LogPlayerTrade(QSPlayerLogTrade_Struct* QS, uint32 Items) { QS->char1_id, QS->char1_money.platinum, QS->char1_money.gold, QS->char1_money.silver, QS->char1_money.copper, QS->char1_count, QS->char2_id, QS->char2_money.platinum, QS->char2_money.gold, QS->char2_money.silver, QS->char2_money.copper, QS->char2_count), errbuf, 0, 0, &lastid)) { - _log(NET__WORLD, "Failed Trade Log Record Insert: %s", errbuf); - _log(NET__WORLD, "%s", query); + _log(QUERYSERV__ERROR, "Failed Trade Log Record Insert: %s", errbuf); + _log(QUERYSERV__ERROR, "%s", query); } - if(Items > 0) { - for(int i = 0; i < Items; i++) { + if(DetailCount > 0) { + for(int i = 0; i < DetailCount; i++) { if(!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO `qs_player_trade_record_entries` SET `event_id`='%i', " "`from_id`='%i', `from_slot`='%i', `to_id`='%i', `to_slot`='%i', `item_id`='%i', " "`charges`='%i', `aug_1`='%i', `aug_2`='%i', `aug_3`='%i', `aug_4`='%i', `aug_5`='%i'", lastid, QS->items[i].from_id, QS->items[i].from_slot, QS->items[i].to_id, QS->items[i].to_slot, QS->items[i].item_id, QS->items[i].charges, QS->items[i].aug_1, QS->items[i].aug_2, QS->items[i].aug_3, QS->items[i].aug_4, QS->items[i].aug_5, errbuf, 0, 0))) { - _log(NET__WORLD, "Failed Trade Log Record Entry Insert: %s", errbuf); - _log(NET__WORLD, "%s", query); + _log(QUERYSERV__ERROR, "Failed Trade Log Record Entry Insert: %s", errbuf); + _log(QUERYSERV__ERROR, "%s", query); } } } } -void Database::LogPlayerHandin(QSPlayerLogHandin_Struct* QS, uint32 Items) { - +void Database::LogPlayerHandin(QSPlayerLogHandin_Struct* QS, uint32 DetailCount) { char errbuf[MYSQL_ERRMSG_SIZE]; char* query = 0; uint32 lastid = 0; @@ -194,20 +159,20 @@ void Database::LogPlayerHandin(QSPlayerLogHandin_Struct* QS, uint32 Items) { QS->quest_id, QS->char_id, QS->char_money.platinum, QS->char_money.gold, QS->char_money.silver, QS->char_money.copper, QS->char_count, QS->npc_id, QS->npc_money.platinum, QS->npc_money.gold, QS->npc_money.silver, QS->npc_money.copper, QS->npc_count), errbuf, 0, 0, &lastid)) { - _log(NET__WORLD, "Failed Handin Log Record Insert: %s", errbuf); - _log(NET__WORLD, "%s", query); + _log(QUERYSERV__ERROR, "Failed Handin Log Record Insert: %s", errbuf); + _log(QUERYSERV__ERROR, "%s", query); } - if(Items > 0) { - for(int i = 0; i < Items; i++) { + if(DetailCount > 0) { + for(int i = 0; i < DetailCount; i++) { if(!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO `qs_player_handin_record_entries` SET `event_id`='%i', " "`action_type`='%s', `char_slot`='%i', `item_id`='%i', `charges`='%i', " "`aug_1`='%i', `aug_2`='%i', `aug_3`='%i', `aug_4`='%i', `aug_5`='%i'", lastid, QS->items[i].action_type, QS->items[i].char_slot, QS->items[i].item_id, QS->items[i].charges, QS->items[i].aug_1, QS->items[i].aug_2, QS->items[i].aug_3, QS->items[i].aug_4, QS->items[i].aug_5, errbuf, 0, 0))) { - _log(NET__WORLD, "Failed Handin Log Record Entry Insert: %s", errbuf); - _log(NET__WORLD, "%s", query); + _log(QUERYSERV__ERROR, "Failed Handin Log Record Entry Insert: %s", errbuf); + _log(QUERYSERV__ERROR, "%s", query); } } } @@ -218,15 +183,15 @@ void Database::LogPlayerNPCKill(QSPlayerLogNPCKill_Struct* QS, uint32 Members){ char* query = 0; uint32 lastid = 0; if(!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO `qs_player_npc_kill_record` SET `npc_id`='%i', `type`='%i', `zone_id`='%i', `time`=NOW()", QS->s1.NPCID, QS->s1.Type, QS->s1.ZoneID), errbuf, 0, 0, &lastid)) { - _log(NET__WORLD, "Failed NPC Kill Log Record Insert: %s", errbuf); - _log(NET__WORLD, "%s", query); + _log(QUERYSERV__ERROR, "Failed NPC Kill Log Record Insert: %s", errbuf); + _log(QUERYSERV__ERROR, "%s", query); } if(Members > 0){ for (int i = 0; i < Members; i++) { if(!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO `qs_player_npc_kill_record_entries` SET `event_id`='%i', `char_id`='%i'", lastid, QS->Chars[i].char_id, errbuf, 0, 0))) { - _log(NET__WORLD, "Failed NPC Kill Log Entry Insert: %s", errbuf); - _log(NET__WORLD, "%s", query); + _log(QUERYSERV__ERROR, "Failed NPC Kill Log Entry Insert: %s", errbuf); + _log(QUERYSERV__ERROR, "%s", query); } } } @@ -241,8 +206,8 @@ void Database::LogPlayerDelete(QSPlayerLogDelete_Struct* QS, uint32 Items) { "`char_id`='%i', `stack_size`='%i', `char_items`='%i'", QS->char_id, QS->stack_size, QS->char_count, QS->char_count), errbuf, 0, 0, &lastid)) { - _log(NET__WORLD, "Failed Delete Log Record Insert: %s", errbuf); - _log(NET__WORLD, "%s", query); + _log(QUERYSERV__ERROR, "Failed Delete Log Record Insert: %s", errbuf); + _log(QUERYSERV__ERROR, "%s", query); } if(Items > 0) { @@ -253,15 +218,15 @@ void Database::LogPlayerDelete(QSPlayerLogDelete_Struct* QS, uint32 Items) { lastid, QS->items[i].char_slot, QS->items[i].item_id, QS->items[i].charges, QS->items[i].aug_1, QS->items[i].aug_2, QS->items[i].aug_3, QS->items[i].aug_4, QS->items[i].aug_5, errbuf, 0, 0))) { - _log(NET__WORLD, "Failed Delete Log Record Entry Insert: %s", errbuf); - _log(NET__WORLD, "%s", query); + _log(QUERYSERV__ERROR, "Failed Delete Log Record Entry Insert: %s", errbuf); + _log(QUERYSERV__ERROR, "%s", query); } } } } -void Database::LogPlayerMove(QSPlayerLogMove_Struct* QS, uint32 Items) { - +void Database::LogPlayerMove(QSPlayerLogMove_Struct* QS, uint32 Items) { + /* These are item moves */ char errbuf[MYSQL_ERRMSG_SIZE]; char* query = 0; uint32 lastid = 0; @@ -269,10 +234,9 @@ void Database::LogPlayerMove(QSPlayerLogMove_Struct* QS, uint32 Items) { "`char_id`='%i', `from_slot`='%i', `to_slot`='%i', `stack_size`='%i', `char_items`='%i', `postaction`='%i'", QS->char_id, QS->from_slot, QS->to_slot, QS->stack_size, QS->char_count, QS->postaction), errbuf, 0, 0, &lastid)) { - _log(NET__WORLD, "Failed Move Log Record Insert: %s", errbuf); - _log(NET__WORLD, "%s", query); - } - + _log(QUERYSERV__ERROR, "Failed Move Log Record Insert: %s", errbuf); + _log(QUERYSERV__ERROR, "%s", query); + } if(Items > 0) { for(int i = 0; i < Items; i++) { if(!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO `qs_player_move_record_entries` SET `event_id`='%i', " @@ -281,16 +245,15 @@ void Database::LogPlayerMove(QSPlayerLogMove_Struct* QS, uint32 Items) { QS->items[i].from_slot, QS->items[i].to_slot, QS->items[i].item_id, QS->items[i].charges, QS->items[i].aug_1, QS->items[i].aug_2, QS->items[i].aug_3, QS->items[i].aug_4, QS->items[i].aug_5, errbuf, 0, 0))) { - _log(NET__WORLD, "Failed Move Log Record Entry Insert: %s", errbuf); - _log(NET__WORLD, "%s", query); + _log(QUERYSERV__ERROR, "Failed Move Log Record Entry Insert: %s", errbuf); + _log(QUERYSERV__ERROR, "%s", query); } } } } void Database::LogMerchantTransaction(QSMerchantLogTransaction_Struct* QS, uint32 Items) { - // Merchant transactions are from the perspective of the merchant, not the player -U - + /* Merchant transactions are from the perspective of the merchant, not the player -U */ char errbuf[MYSQL_ERRMSG_SIZE]; char* query = 0; uint32 lastid = 0; @@ -300,8 +263,8 @@ void Database::LogMerchantTransaction(QSMerchantLogTransaction_Struct* QS, uint3 QS->zone_id, QS->merchant_id, QS->merchant_money.platinum, QS->merchant_money.gold, QS->merchant_money.silver, QS->merchant_money.copper, QS->merchant_count, QS->char_id, QS->char_money.platinum, QS->char_money.gold, QS->char_money.silver, QS->char_money.copper, QS->char_count), errbuf, 0, 0, &lastid)) { - _log(NET__WORLD, "Failed Transaction Log Record Insert: %s", errbuf); - _log(NET__WORLD, "%s", query); + _log(QUERYSERV__ERROR, "Failed Transaction Log Record Insert: %s", errbuf); + _log(QUERYSERV__ERROR, "%s", query); } if(Items > 0) { @@ -312,10 +275,29 @@ void Database::LogMerchantTransaction(QSMerchantLogTransaction_Struct* QS, uint3 lastid, QS->items[i].char_slot, QS->items[i].item_id, QS->items[i].charges, QS->items[i].aug_1, QS->items[i].aug_2, QS->items[i].aug_3, QS->items[i].aug_4, QS->items[i].aug_5, errbuf, 0, 0))) { - _log(NET__WORLD, "Failed Transaction Log Record Entry Insert: %s", errbuf); - _log(NET__WORLD, "%s", query); + _log(QUERYSERV__ERROR, "Failed Transaction Log Record Entry Insert: %s", errbuf); + _log(QUERYSERV__ERROR, "%s", query); } } } + safe_delete_array(query); } +void Database::GeneralQueryReceive(ServerPacket *pack) { + /* + These are general queries passed from anywhere in zone instead of packing structures and breaking them down again and again + */ + char *Query = nullptr; + Query = new char[pack->ReadUInt32() + 1]; + pack->ReadString(Query); + char errbuf[MYSQL_ERRMSG_SIZE]; + char* query = 0; + uint32 lastid = 0; + if (!RunQuery(query, MakeAnyLenString(&query, Query), errbuf, 0, 0, &lastid)) { + _log(QUERYSERV__ERROR, "Failed Delete Log Record Insert: %s", errbuf); + _log(QUERYSERV__ERROR, "%s", query); + } + safe_delete_array(query); + safe_delete(pack); + safe_delete(Query); +} diff --git a/queryserv/database.h b/queryserv/database.h index 61664f90d..a25d91611 100644 --- a/queryserv/database.h +++ b/queryserv/database.h @@ -42,14 +42,14 @@ public: bool Connect(const char* host, const char* user, const char* passwd, const char* database,uint32 port); ~Database(); - bool GetVariable(const char* varname, char* varvalue, uint16 varvalue_len); void AddSpeech(const char* from, const char* to, const char* message, uint16 minstatus, uint32 guilddbid, uint8 type); - void LogPlayerTrade(QSPlayerLogTrade_Struct* QS, uint32 Items); - void LogPlayerHandin(QSPlayerLogHandin_Struct* QS, uint32 Items); + void LogPlayerTrade(QSPlayerLogTrade_Struct* QS, uint32 DetailCount); + void LogPlayerHandin(QSPlayerLogHandin_Struct* QS, uint32 DetailCount); void LogPlayerNPCKill(QSPlayerLogNPCKill_Struct* QS, uint32 Members); void LogPlayerDelete(QSPlayerLogDelete_Struct* QS, uint32 Items); void LogPlayerMove(QSPlayerLogMove_Struct* QS, uint32 Items); void LogMerchantTransaction(QSMerchantLogTransaction_Struct* QS, uint32 Items); + void GeneralQueryReceive(ServerPacket *pack); protected: void HandleMysqlError(uint32 errnum); private: diff --git a/queryserv/lfguild.cpp b/queryserv/lfguild.cpp index 9fc4d0b62..c135d4991 100644 --- a/queryserv/lfguild.cpp +++ b/queryserv/lfguild.cpp @@ -22,7 +22,7 @@ PlayerLookingForGuild::PlayerLookingForGuild(char *Name, char *Comments, uint32 GuildLookingForPlayers::GuildLookingForPlayers(char *Name, char *Comments, uint32 FromLevel, uint32 ToLevel, uint32 Classes, uint32 AACount, uint32 Timezone, uint32 TimePosted) { - this->Name = Name; + this->Name = Name; this->Comments = Comments; this->FromLevel = FromLevel; this->ToLevel = ToLevel; diff --git a/queryserv/queryserv.cpp b/queryserv/queryserv.cpp index 3827ceea1..22ddb87ee 100644 --- a/queryserv/queryserv.cpp +++ b/queryserv/queryserv.cpp @@ -33,56 +33,47 @@ volatile bool RunLoops = true; -uint32 MailMessagesSent = 0; -uint32 ChatMessagesSent = 0; - TimeoutManager timeout_manager; - Database database; LFGuildManager lfguildmanager; std::string WorldShortName; - const queryservconfig *Config; - WorldServer *worldserver = 0; - -void CatchSignal(int sig_num) { - - RunLoops = false; - +void CatchSignal(int sig_num) { + RunLoops = false; if(worldserver) worldserver->Disconnect(); } int main() { RegisterExecutablePlatform(ExePlatformQueryServ); - set_exception_handler(); - - Timer LFGuildExpireTimer(60000); - + set_exception_handler(); + Timer LFGuildExpireTimer(60000); Timer InterserverTimer(INTERSERVER_TIMER); // does auto-reconnect + /* Load XML from eqemu_config.xml + + 127.0.0.1 + 3306 + user + password + dbname + + */ + _log(QUERYSERV__INIT, "Starting EQEmu QueryServ."); - if (!queryservconfig::LoadConfig()) { - _log(QUERYSERV__INIT, "Loading server configuration failed."); - return 1; } - Config = queryservconfig::get(); - - if(!load_log_settings(Config->LogSettingsFile.c_str())) - _log(QUERYSERV__INIT, "Warning: Unable to read %s", Config->LogSettingsFile.c_str()); - else - _log(QUERYSERV__INIT, "Log settings loaded from %s", Config->LogSettingsFile.c_str()); - - WorldShortName = Config->ShortName; + Config = queryservconfig::get(); + WorldShortName = Config->ShortName; _log(QUERYSERV__INIT, "Connecting to MySQL..."); - + + /* MySQL Connection */ if (!database.Connect( Config->QSDatabaseHost.c_str(), Config->QSDatabaseUsername.c_str(), @@ -93,6 +84,12 @@ int main() { return 1; } + /* Initialize Logging */ + if (!load_log_settings(Config->LogSettingsFile.c_str())) + _log(QUERYSERV__INIT, "Warning: Unable to read %s", Config->LogSettingsFile.c_str()); + else + _log(QUERYSERV__INIT, "Log settings loaded from %s", Config->LogSettingsFile.c_str()); + if (signal(SIGINT, CatchSignal) == SIG_ERR) { _log(QUERYSERV__ERROR, "Could not set signal handler"); return 1; @@ -102,16 +99,15 @@ int main() { return 1; } + /* Initial Connection to Worldserver */ worldserver = new WorldServer; + worldserver->Connect(); - worldserver->Connect(); - + /* Load Looking For Guild Manager */ lfguildmanager.LoadDatabase(); - while(RunLoops) { - - Timer::SetCurrentTime(); - + while(RunLoops) { + Timer::SetCurrentTime(); if(LFGuildExpireTimer.Check()) lfguildmanager.ExpireEntries(); @@ -119,10 +115,8 @@ int main() { if (worldserver->TryReconnect() && (!worldserver->Connected())) worldserver->AsyncConnect(); } - worldserver->Process(); - - timeout_manager.CheckTimeouts(); - + worldserver->Process(); + timeout_manager.CheckTimeouts(); Sleep(100); } } diff --git a/queryserv/worldserver.cpp b/queryserv/worldserver.cpp index bb7523592..fc87929ca 100644 --- a/queryserv/worldserver.cpp +++ b/queryserv/worldserver.cpp @@ -56,122 +56,107 @@ void WorldServer::OnConnected() void WorldServer::Process() { - WorldConnection::Process(); - + WorldConnection::Process(); if (!Connected()) return; - ServerPacket *pack = 0; - + ServerPacket *pack = 0; while((pack = tcpc.PopPacket())) { - _log(QUERYSERV__TRACE, "Received Opcode: %4X", pack->opcode); - - switch(pack->opcode) - { + _log(QUERYSERV__TRACE, "Received Opcode: %4X", pack->opcode); + switch(pack->opcode) { case 0: { break; } - case ServerOP_KeepAlive: - { + case ServerOP_KeepAlive: { break; } - case ServerOP_Speech: - { - Server_Speech_Struct *SSS = (Server_Speech_Struct*)pack->pBuffer; - + case ServerOP_Speech: { + Server_Speech_Struct *SSS = (Server_Speech_Struct*)pack->pBuffer; std::string tmp1 = SSS->from; - std::string tmp2 = SSS->to; - + std::string tmp2 = SSS->to; database.AddSpeech(tmp1.c_str(), tmp2.c_str(), SSS->message, SSS->minstatus, SSS->guilddbid, SSS->type); break; } - case ServerOP_QSPlayerLogTrades: - { + case ServerOP_QSPlayerLogTrades: { QSPlayerLogTrade_Struct *QS = (QSPlayerLogTrade_Struct*)pack->pBuffer; - uint32 Items = QS->char1_count + QS->char2_count; - database.LogPlayerTrade(QS, Items); + database.LogPlayerTrade(QS, QS->_detail_count); break; } - case ServerOP_QSPlayerLogHandins: - { + case ServerOP_QSPlayerLogHandins: { QSPlayerLogHandin_Struct *QS = (QSPlayerLogHandin_Struct*)pack->pBuffer; - uint32 Items = QS->char_count + QS->npc_count; - database.LogPlayerHandin(QS, Items); + database.LogPlayerHandin(QS, QS->_detail_count); break; } - case ServerOP_QSPlayerLogNPCKills: - { + case ServerOP_QSPlayerLogNPCKills: { QSPlayerLogNPCKill_Struct *QS = (QSPlayerLogNPCKill_Struct*)pack->pBuffer; uint32 Members = pack->size - sizeof(QSPlayerLogNPCKill_Struct); if (Members > 0) Members = Members / sizeof(QSPlayerLogNPCKillsPlayers_Struct); database.LogPlayerNPCKill(QS, Members); break; } - case ServerOP_QSPlayerLogDeletes: - { + case ServerOP_QSPlayerLogDeletes: { QSPlayerLogDelete_Struct *QS = (QSPlayerLogDelete_Struct*)pack->pBuffer; uint32 Items = QS->char_count; database.LogPlayerDelete(QS, Items); break; } - case ServerOP_QSPlayerLogMoves: - { + case ServerOP_QSPlayerLogMoves: { QSPlayerLogMove_Struct *QS = (QSPlayerLogMove_Struct*)pack->pBuffer; uint32 Items = QS->char_count; database.LogPlayerMove(QS, Items); break; } - case ServerOP_QSMerchantLogTransactions: - { + case ServerOP_QSPlayerLogMerchantTransactions: { QSMerchantLogTransaction_Struct *QS = (QSMerchantLogTransaction_Struct*)pack->pBuffer; uint32 Items = QS->char_count + QS->merchant_count; database.LogMerchantTransaction(QS, Items); - break; + break; } - case ServerOP_QueryServGeneric: - { - // The purpose of ServerOP_QueryServerGeneric is so that we don't have to add code to world just to relay packets - // each time we add functionality to queryserv. - // - // A ServerOP_QueryServGeneric packet has the following format: - // - // uint32 SourceZoneID - // uint32 SourceInstanceID - // char OriginatingCharacterName[0] // Null terminated name of the character this packet came from. This could be just - // // an empty string if it has no meaning in the context of a particular packet. - // uint32 Type - // - // The 'Type' field is a 'sub-opcode'. A value of 0 is used for the LFGuild packets. The next feature to be added - // to queryserv would use 1, etc. - // - // Obviously, any fields in the packet following the 'Type' will be unique to the particular type of packet. The - // 'Generic' in the name of this ServerOP code relates to the four header fields. + case ServerOP_QueryServGeneric: { + /* + The purpose of ServerOP_QueryServerGeneric is so that we don't have to add code to world just to relay packets + each time we add functionality to queryserv. + + A ServerOP_QueryServGeneric packet has the following format: + + uint32 SourceZoneID + uint32 SourceInstanceID + char OriginatingCharacterName[0] + - Null terminated name of the character this packet came from. This could be just + - an empty string if it has no meaning in the context of a particular packet. + uint32 Type + + The 'Type' field is a 'sub-opcode'. A value of 0 is used for the LFGuild packets. The next feature to be added + to queryserv would use 1, etc. + + Obviously, any fields in the packet following the 'Type' will be unique to the particular type of packet. The + 'Generic' in the name of this ServerOP code relates to the four header fields. + */ + char From[64]; pack->SetReadPosition(8); pack->ReadString(From); uint32 Type = pack->ReadUInt32(); - switch(Type) - { - case QSG_LFGuild: - { - lfguildmanager.HandlePacket(pack); + switch(Type) { + case QSG_LFGuild:{ + lfguildmanager.HandlePacket(pack); break; } - default: _log(QUERYSERV__ERROR, "Received unhandled ServerOP_QueryServGeneric", Type); break; } - break; } - - + case ServerOP_QSSendQuery: { + /* Process all packets here */ + database.GeneralQueryReceive(pack); + break; + } } - } - + } safe_delete(pack); return; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4c4f32f0d..f7972e98f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,12 +7,13 @@ SET(tests_sources ) SET(tests_headers + atobool_test.h fixed_memory_test.h fixed_memory_variable_test.h + hextoi_32_64_test.h ipc_mutex_test.h memory_mapped_file_test.h - atobool_test.h - hextoi_32_64_test.h + string_util_test.h ) ADD_EXECUTABLE(tests ${tests_sources} ${tests_headers}) diff --git a/tests/main.cpp b/tests/main.cpp index fd0862edb..285460da4 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -26,6 +26,7 @@ #include "fixed_memory_variable_test.h" #include "atobool_test.h" #include "hextoi_32_64_test.h" +#include "string_util_test.h" int main() { try { @@ -38,6 +39,7 @@ int main() { tests.add(new FixedMemoryVariableHashTest()); tests.add(new atoboolTest()); tests.add(new hextoi_32_64_Test()); + tests.add(new StringUtilTest()); tests.run(*output, true); } catch(...) { return -1; diff --git a/tests/string_util_test.h b/tests/string_util_test.h new file mode 100644 index 000000000..32efb5320 --- /dev/null +++ b/tests/string_util_test.h @@ -0,0 +1,85 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2013 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 __EQEMU_TESTS_STRING_UTIL_H +#define __EQEMU_TESTS_STRING_UTIL_H + +#include "cppunit/cpptest.h" +#include "../common/string_util.h" + +class StringUtilTest : public Test::Suite { + typedef void(IPCMutexTest::*TestFunction)(void); +public: + StringUtilTest() { + TEST_ADD(StringUtilTest::StringFormatTest); + TEST_ADD(StringUtilTest::EscapeStringTest); + TEST_ADD(StringUtilTest::EscapeStringMemoryTest); + } + + ~StringUtilTest() { + } + + private: + void StringFormatTest() { + const char* fmt = "Test: %c %d %4.2f"; + char c = 'a'; + int i = 2014; + float f = 3.1416; + + auto s = StringFormat(fmt, c, i, f); + TEST_ASSERT_EQUALS(s.length(), 17); + TEST_ASSERT(s.compare("Test: a 2014 3.14") == 0); + } + + void EscapeStringTest() { + std::string t; + t.resize(10); + t[0] = 'a'; + t[1] = 'b'; + t[2] = 'c'; + t[3] = '\x00'; + t[4] = '\n'; + t[5] = '\r'; + t[6] = '\\'; + t[7] = '\''; + t[8] = '\"'; + t[9] = '\x1a'; + + auto s = EscapeString(t); + TEST_ASSERT(s.compare("abc\\x00\\n\\r\\\\\\'\\\"\\x1a") == 0); + } + + void EscapeStringMemoryTest() { + char t[10] = { 0 }; + t[0] = 'a'; + t[1] = 'b'; + t[2] = 'c'; + t[3] = '\x00'; + t[4] = '\n'; + t[5] = '\r'; + t[6] = '\\'; + t[7] = '\''; + t[8] = '\"'; + t[9] = '\x1a'; + + auto s = EscapeString(t, 10); + TEST_ASSERT(s.compare("abc\\x00\\n\\r\\\\\\'\\\"\\x1a") == 0); + } +}; + +#endif diff --git a/ucs/database.cpp b/ucs/database.cpp index 4d055e2a4..8eb8746b3 100644 --- a/ucs/database.cpp +++ b/ucs/database.cpp @@ -102,208 +102,143 @@ Database::~Database() { } -void Database::GetAccountStatus(Client *c) { - - char errbuf[MYSQL_ERRMSG_SIZE]; - char* query = 0; - MYSQL_RES *result; - MYSQL_ROW row; - - if (!RunQuery(query,MakeAnyLenString(&query, "select `status`, `hideme`, `karma`, `revoked` from `account` where `id`='%i' limit 1", - c->GetAccountID()),errbuf,&result)){ - - _log(UCS__ERROR, "Unable to get account status for character %s, error %s", c->GetName().c_str(), errbuf); - - safe_delete_array(query); +void Database::GetAccountStatus(Client *client) { + std::string query = StringFormat("SELECT `status`, `hideme`, `karma`, `revoked` " + "FROM `account` WHERE `id` = '%i' LIMIT 1", + client->GetAccountID()); + auto results = QueryDatabase(query); + if (!results.Success()) { + _log(UCS__ERROR, "Unable to get account status for character %s, error %s", client->GetName().c_str(), results.ErrorMessage().c_str()); return; } - _log(UCS__TRACE, "GetAccountStatus Query: %s", query); - safe_delete_array(query); - if(mysql_num_rows(result) != 1) + _log(UCS__TRACE, "GetAccountStatus Query: %s", query.c_str()); + + if(results.RowCount() != 1) { _log(UCS__ERROR, "Error in GetAccountStatus"); - mysql_free_result(result); return; } - row = mysql_fetch_row(result); + auto row = results.begin(); - c->SetAccountStatus(atoi(row[0])); - c->SetHideMe(atoi(row[1]) != 0); - c->SetKarma(atoi(row[2])); - c->SetRevoked((atoi(row[3])==1?true:false)); + client->SetAccountStatus(atoi(row[0])); + client->SetHideMe(atoi(row[1]) != 0); + client->SetKarma(atoi(row[2])); + client->SetRevoked((atoi(row[3])==1?true:false)); + + _log(UCS__TRACE, "Set account status to %i, hideme to %i and karma to %i for %s", client->GetAccountStatus(), client->GetHideMe(), client->GetKarma(), client->GetName().c_str()); - _log(UCS__TRACE, "Set account status to %i, hideme to %i and karma to %i for %s", c->GetAccountStatus(), c->GetHideMe(), c->GetKarma(), c->GetName().c_str()); - mysql_free_result(result); } -int Database::FindAccount(const char *CharacterName, Client *c) { +int Database::FindAccount(const char *characterName, Client *client) { - _log(UCS__TRACE, "FindAccount for character %s", CharacterName); + _log(UCS__TRACE, "FindAccount for character %s", characterName); - char errbuf[MYSQL_ERRMSG_SIZE]; - char* query = 0; - MYSQL_RES *result; - MYSQL_ROW row; - c->ClearCharacters(); - - if (!RunQuery(query,MakeAnyLenString(&query, "select `id`, `account_id`, `level` from `character_` where `name`='%s' limit 1", - CharacterName),errbuf,&result)) - { - _log(UCS__ERROR, "FindAccount query failed: %s", query); - safe_delete_array(query); + client->ClearCharacters(); + std::string query = StringFormat("SELECT `id`, `account_id`, `level` " + "FROM `character_` WHERE `name` = '%s' LIMIT 1", + characterName); + auto results = QueryDatabase(query); + if (!results.Success()) { + _log(UCS__ERROR, "FindAccount query failed: %s", query.c_str()); return -1; } - safe_delete_array(query); - if (mysql_num_rows(result) != 1) - { + if (results.RowCount() != 1) { _log(UCS__ERROR, "Bad result from query"); - mysql_free_result(result); return -1; } - row = mysql_fetch_row(result); - c->AddCharacter(atoi(row[0]), CharacterName, atoi(row[2])); - int AccountID = atoi(row[1]); + auto row = results.begin(); + client->AddCharacter(atoi(row[0]), characterName, atoi(row[2])); - mysql_free_result(result); - _log(UCS__TRACE, "Account ID for %s is %i", CharacterName, AccountID); + int accountID = atoi(row[1]); - if (!RunQuery(query,MakeAnyLenString(&query, "select `id`, `name`, `level` from `character_` where `account_id`=%i and `name` !='%s'", - AccountID, CharacterName),errbuf,&result)) - { - safe_delete_array(query); - return AccountID; - } - safe_delete_array(query); + _log(UCS__TRACE, "Account ID for %s is %i", characterName, accountID); - for(unsigned int i = 0; i < mysql_num_rows(result); i++) - { - row = mysql_fetch_row(result); - c->AddCharacter(atoi(row[0]), row[1], atoi(row[2])); - } - mysql_free_result(result); - return AccountID; + query = StringFormat("SELECT `id`, `name`, `level` FROM `character_` " + "WHERE `account_id` = %i AND `name` != '%s'", + accountID, characterName); + results = QueryDatabase(query); + if (!results.Success()) + return accountID; + + for (auto row = results.begin(); row != results.end(); ++row) + client->AddCharacter(atoi(row[0]), row[1], atoi(row[2])); + + return accountID; } -bool Database::VerifyMailKey(std::string CharacterName, int IPAddress, std::string MailKey) { - - char errbuf[MYSQL_ERRMSG_SIZE]; - char* query = 0; - MYSQL_RES *result; - MYSQL_ROW row; - - if (!RunQuery(query,MakeAnyLenString(&query, "select `mailkey` from `character_` where `name`='%s' limit 1", - CharacterName.c_str()),errbuf,&result)){ - - safe_delete_array(query); - - _log(UCS__ERROR, "Error retrieving mailkey from database: %s", errbuf); +bool Database::VerifyMailKey(std::string characterName, int IPAddress, std::string MailKey) { + std::string query = StringFormat("SELECT `mailkey` FROM `character_` WHERE `name`='%s' LIMIT 1", + characterName.c_str()); + auto results = QueryDatabase(query); + if (!results.Success()) { + _log(UCS__ERROR, "Error retrieving mailkey from database: %s", results.ErrorMessage().c_str()); return false; } - safe_delete_array(query); - - row = mysql_fetch_row(result); + auto row = results.begin(); // The key is the client's IP address (expressed as 8 hex digits) and an 8 hex digit random string generated // by world. // - char CombinedKey[17]; + char combinedKey[17]; if(RuleB(Chat, EnableMailKeyIPVerification) == true) - sprintf(CombinedKey, "%08X%s", IPAddress, MailKey.c_str()); + sprintf(combinedKey, "%08X%s", IPAddress, MailKey.c_str()); else - sprintf(CombinedKey, "%s", MailKey.c_str()); + sprintf(combinedKey, "%s", MailKey.c_str()); - _log(UCS__TRACE, "DB key is [%s], Client key is [%s]", row[0], CombinedKey); - - bool Valid = !strcmp(row[0], CombinedKey); - - mysql_free_result(result); - - return Valid; + _log(UCS__TRACE, "DB key is [%s], Client key is [%s]", row[0], combinedKey); + return !strcmp(row[0], combinedKey); } -int Database::FindCharacter(const char *CharacterName) { +int Database::FindCharacter(const char *characterName) { - char errbuf[MYSQL_ERRMSG_SIZE]; - char* query = 0; - MYSQL_RES *result; - MYSQL_ROW row; - - char *SafeCharName = RemoveApostrophes(CharacterName); - - if (!RunQuery(query,MakeAnyLenString(&query, "select `id` from `character_` where `name`='%s' limit 1", - SafeCharName),errbuf,&result)){ - - _log(UCS__ERROR, "FindCharacter failed. %s %s", query, errbuf); - - safe_delete_array(query); - safe_delete_array(SafeCharName); + char *safeCharName = RemoveApostrophes(characterName); + std::string query = StringFormat("SELECT `id` FROM `character_` WHERE `name`='%s' LIMIT 1", safeCharName); + auto results = QueryDatabase(query); + if (!results.Success()) { + _log(UCS__ERROR, "FindCharacter failed. %s %s", query.c_str(), results.ErrorMessage().c_str()); + safe_delete(safeCharName); + return -1; + } + safe_delete(safeCharName); + if (results.RowCount() != 1) { + _log(UCS__ERROR, "Bad result from FindCharacter query for character %s", characterName); return -1; } - safe_delete_array(query); - safe_delete_array(SafeCharName); + auto row = results.begin(); - if (mysql_num_rows(result) != 1) { - - _log(UCS__ERROR, "Bad result from FindCharacter query for character %s", CharacterName); - - mysql_free_result(result); - - return -1; - } - - row = mysql_fetch_row(result); - - int CharacterID = atoi(row[0]); - - mysql_free_result(result); - - return CharacterID; + int characterID = atoi(row[0]); + return characterID; } bool Database::GetVariable(const char* varname, char* varvalue, uint16 varvalue_len) { - char errbuf[MYSQL_ERRMSG_SIZE]; - char* query = 0; - MYSQL_RES *result; - MYSQL_ROW row; - - if (!RunQuery(query,MakeAnyLenString(&query, "select `value` from `variables` where `varname`='%s'", varname), errbuf, &result)) { - - _log(UCS__ERROR, "Unable to get message count from database. %s %s", query, errbuf); - - safe_delete_array(query); - + std::string query = StringFormat("SELECT `value` FROM `variables` WHERE `varname` = '%s'", varname); + auto results = QueryDatabase(query); + if (!results.Success()) { + _log(UCS__ERROR, "Unable to get message count from database. %s %s", query.c_str(), results.ErrorMessage().c_str()); return false; } - safe_delete_array(query); - - if (mysql_num_rows(result) != 1) { - - mysql_free_result(result); - + if (results.RowCount() != 1) return false; - } - row = mysql_fetch_row(result); + auto row = results.begin(); snprintf(varvalue, varvalue_len, "%s", row[0]); - mysql_free_result(result); - return true; } @@ -311,302 +246,238 @@ bool Database::LoadChatChannels() { _log(UCS__INIT, "Loading chat channels from the database."); - char errbuf[MYSQL_ERRMSG_SIZE]; - char* query = 0; - MYSQL_RES *result; - MYSQL_ROW row; - - if (!RunQuery(query,MakeAnyLenString(&query, "select `name`,`owner`,`password`, `minstatus` from `chatchannels`"),errbuf,&result)){ - - _log(UCS__ERROR, "Failed to load channels. %s %s", query, errbuf); - safe_delete_array(query); - + const std::string query = "SELECT `name`, `owner`, `password`, `minstatus` FROM `chatchannels`"; + auto results = QueryDatabase(query); + if (!results.Success()) { + _log(UCS__ERROR, "Failed to load channels. %s %s", query.c_str(), results.ErrorMessage().c_str()); return false; } - safe_delete_array(query); + for (auto row = results.begin();row != results.end(); ++row) { + std::string channelName = row[0]; + std::string channelOwner = row[1]; + std::string channelPassword = row[2]; - while((row = mysql_fetch_row(result))) { - - std::string ChannelName = row[0]; - std::string ChannelOwner = row[1]; - std::string ChannelPassword = row[2]; - - ChannelList->CreateChannel(ChannelName, ChannelOwner, ChannelPassword, true, atoi(row[3])); + ChannelList->CreateChannel(channelName, channelOwner, channelPassword, true, atoi(row[3])); } - mysql_free_result(result); - return true; } -void Database::SetChannelPassword(std::string ChannelName, std::string Password) { +void Database::SetChannelPassword(std::string channelName, std::string password) { - _log(UCS__TRACE, "Database::SetChannelPassword(%s, %s)", ChannelName.c_str(), Password.c_str()); + _log(UCS__TRACE, "Database::SetChannelPassword(%s, %s)", channelName.c_str(), password.c_str()); - char errbuf[MYSQL_ERRMSG_SIZE]; - char *query = 0; + std::string query = StringFormat("UPDATE `chatchannels` SET `password` = '%s' WHERE `name` = '%s'", + password.c_str(), channelName.c_str()); + auto results = QueryDatabase(query); + if(!results.Success()) + _log(UCS__ERROR, "Error updating password in database: %s, %s", query.c_str(), results.ErrorMessage().c_str()); - if(!RunQuery(query, MakeAnyLenString(&query, "UPDATE `chatchannels` set `password`='%s' where `name`='%s'", Password.c_str(), - ChannelName.c_str()), errbuf)) { - - _log(UCS__ERROR, "Error updating password in database: %s, %s", query, errbuf); - - } - - safe_delete_array(query); } -void Database::SetChannelOwner(std::string ChannelName, std::string Owner) { +void Database::SetChannelOwner(std::string channelName, std::string owner) { - _log(UCS__TRACE, "Database::SetChannelOwner(%s, %s)", ChannelName.c_str(), Owner.c_str()); + _log(UCS__TRACE, "Database::SetChannelOwner(%s, %s)", channelName.c_str(), owner.c_str()); - char errbuf[MYSQL_ERRMSG_SIZE]; - char *query = 0; + std::string query = StringFormat("UPDATE `chatchannels` SET `owner` = '%s' WHERE `name` = '%s'", + owner.c_str(), channelName.c_str()); + auto results = QueryDatabase(query); + if(!results.Success()) + _log(UCS__ERROR, "Error updating Owner in database: %s, %s", query.c_str(), results.ErrorMessage().c_str()); - if(!RunQuery(query, MakeAnyLenString(&query, "UPDATE `chatchannels` set `owner`='%s' where `name`='%s'", Owner.c_str(), - ChannelName.c_str()), errbuf)) { - - _log(UCS__ERROR, "Error updating Owner in database: %s, %s", query, errbuf); - - } - - safe_delete_array(query); } -void Database::SendHeaders(Client *c) { +void Database::SendHeaders(Client *client) { - int UnknownField2 = 25015275; - int UnknownField3 = 1; + int unknownField2 = 25015275; + int unknownField3 = 1; + int characterID = FindCharacter(client->MailBoxName().c_str()); - int CharacterID = FindCharacter(c->MailBoxName().c_str()); - _log(UCS__TRACE, "Sendheaders for %s, CharID is %i", c->MailBoxName().c_str(), CharacterID); - if(CharacterID <= 0) + _log(UCS__TRACE, "Sendheaders for %s, CharID is %i", client->MailBoxName().c_str(), characterID); + + if(characterID <= 0) return; + std::string query = StringFormat("SELECT `msgid`,`timestamp`, `from`, `subject`, `status` " + "FROM `mail` WHERE `charid`=%i", characterID); + auto results = QueryDatabase(query); + if (!results.Success()) + return; - char errbuf[MYSQL_ERRMSG_SIZE]; - char* query = 0; - MYSQL_RES *result; - MYSQL_ROW row; + char buffer[100]; - if (!RunQuery(query,MakeAnyLenString(&query, "select `msgid`,`timestamp`,`from`,`subject`, `status` from `mail` " - "where `charid`=%i", CharacterID),errbuf,&result)){ + int headerCountPacketLength = 0; - safe_delete_array(query); + sprintf(buffer, "%i", client->GetMailBoxNumber()); + headerCountPacketLength += (strlen(buffer) + 1); - return ; - } + sprintf(buffer, "%i", unknownField2); + headerCountPacketLength += (strlen(buffer) + 1); - safe_delete_array(query); + sprintf(buffer, "%i", unknownField3); + headerCountPacketLength += (strlen(buffer) + 1); - char Buf[100]; + sprintf(buffer, "%i", results.RowCount()); + headerCountPacketLength += (strlen(buffer) + 1); - uint32 NumRows = mysql_num_rows(result); + EQApplicationPacket *outapp = new EQApplicationPacket(OP_MailHeaderCount, headerCountPacketLength); - int HeaderCountPacketLength = 0; + char *packetBuffer = (char *)outapp->pBuffer; - sprintf(Buf, "%i", c->GetMailBoxNumber()); - HeaderCountPacketLength += (strlen(Buf) + 1); - - sprintf(Buf, "%i", UnknownField2); - HeaderCountPacketLength += (strlen(Buf) + 1); - - sprintf(Buf, "%i", UnknownField3); - HeaderCountPacketLength += (strlen(Buf) + 1); - - sprintf(Buf, "%i", NumRows); - HeaderCountPacketLength += (strlen(Buf) + 1); - - EQApplicationPacket *outapp = new EQApplicationPacket(OP_MailHeaderCount, HeaderCountPacketLength); - - char *PacketBuffer = (char *)outapp->pBuffer; - - VARSTRUCT_ENCODE_INTSTRING(PacketBuffer, c->GetMailBoxNumber()); - VARSTRUCT_ENCODE_INTSTRING(PacketBuffer, UnknownField2); - VARSTRUCT_ENCODE_INTSTRING(PacketBuffer, UnknownField3); - VARSTRUCT_ENCODE_INTSTRING(PacketBuffer, NumRows); + VARSTRUCT_ENCODE_INTSTRING(packetBuffer, client->GetMailBoxNumber()); + VARSTRUCT_ENCODE_INTSTRING(packetBuffer, unknownField2); + VARSTRUCT_ENCODE_INTSTRING(packetBuffer, unknownField3); + VARSTRUCT_ENCODE_INTSTRING(packetBuffer, results.RowCount()); _pkt(UCS__PACKETS, outapp); - c->QueuePacket(outapp); + client->QueuePacket(outapp); safe_delete(outapp); - int RowNum = 0; + int rowIndex = 0; + for(auto row = results.begin(); row != results.end(); ++row, ++rowIndex) { + int headerPacketLength = 0; - while((row = mysql_fetch_row(result))) { + sprintf(buffer, "%i", client->GetMailBoxNumber()); + headerPacketLength += strlen(buffer) + 1; + sprintf(buffer, "%i", unknownField2); + headerPacketLength += strlen(buffer) + 1; + sprintf(buffer, "%i", rowIndex); + headerPacketLength += strlen(buffer) + 1; + headerPacketLength += strlen(row[0]) + 1; + headerPacketLength += strlen(row[1]) + 1; + headerPacketLength += strlen(row[4]) + 1; + headerPacketLength += GetMailPrefix().length() + strlen(row[2]) + 1; + headerPacketLength += strlen(row[3]) + 1; - int HeaderPacketLength = 0; + outapp = new EQApplicationPacket(OP_MailHeader, headerPacketLength); - sprintf(Buf, "%i", c->GetMailBoxNumber()); - HeaderPacketLength = HeaderPacketLength + strlen(Buf) + 1; - sprintf(Buf, "%i", UnknownField2); - HeaderPacketLength = HeaderPacketLength + strlen(Buf) + 1; - sprintf(Buf, "%i", RowNum); - HeaderPacketLength = HeaderPacketLength + strlen(Buf) + 1; + packetBuffer = (char *)outapp->pBuffer; - HeaderPacketLength = HeaderPacketLength + strlen(row[0]) + 1; - HeaderPacketLength = HeaderPacketLength + strlen(row[1]) + 1; - HeaderPacketLength = HeaderPacketLength + strlen(row[4]) + 1; - HeaderPacketLength = HeaderPacketLength + GetMailPrefix().length() + strlen(row[2]) + 1; - HeaderPacketLength = HeaderPacketLength + strlen(row[3]) + 1; - - outapp = new EQApplicationPacket(OP_MailHeader, HeaderPacketLength); - - PacketBuffer = (char *)outapp->pBuffer; - - VARSTRUCT_ENCODE_INTSTRING(PacketBuffer, c->GetMailBoxNumber()); - VARSTRUCT_ENCODE_INTSTRING(PacketBuffer, UnknownField2); - VARSTRUCT_ENCODE_INTSTRING(PacketBuffer, RowNum); - VARSTRUCT_ENCODE_STRING(PacketBuffer, row[0]); - VARSTRUCT_ENCODE_STRING(PacketBuffer, row[1]); - VARSTRUCT_ENCODE_STRING(PacketBuffer, row[4]); - VARSTRUCT_ENCODE_STRING(PacketBuffer, GetMailPrefix().c_str()); PacketBuffer--; - VARSTRUCT_ENCODE_STRING(PacketBuffer, row[2]); - VARSTRUCT_ENCODE_STRING(PacketBuffer, row[3]); + VARSTRUCT_ENCODE_INTSTRING(packetBuffer, client->GetMailBoxNumber()); + VARSTRUCT_ENCODE_INTSTRING(packetBuffer, unknownField2); + VARSTRUCT_ENCODE_INTSTRING(packetBuffer, rowIndex); + VARSTRUCT_ENCODE_STRING(packetBuffer, row[0]); + VARSTRUCT_ENCODE_STRING(packetBuffer, row[1]); + VARSTRUCT_ENCODE_STRING(packetBuffer, row[4]); + VARSTRUCT_ENCODE_STRING(packetBuffer, GetMailPrefix().c_str()); + packetBuffer--; + VARSTRUCT_ENCODE_STRING(packetBuffer, row[2]); + VARSTRUCT_ENCODE_STRING(packetBuffer, row[3]); _pkt(UCS__PACKETS, outapp); - c->QueuePacket(outapp); + client->QueuePacket(outapp); safe_delete(outapp); - - RowNum++; } - mysql_free_result(result); - } -void Database::SendBody(Client *c, int MessageNumber) { +void Database::SendBody(Client *client, int messageNumber) { - int CharacterID = FindCharacter(c->MailBoxName().c_str()); + int characterID = FindCharacter(client->MailBoxName().c_str()); - _log(UCS__TRACE, "SendBody: MsgID %i, to %s, CharID is %i", MessageNumber, c->MailBoxName().c_str(), CharacterID); + _log(UCS__TRACE, "SendBody: MsgID %i, to %s, CharID is %i", messageNumber, client->MailBoxName().c_str(), characterID); - if(CharacterID <= 0) + if(characterID <= 0) return; - char errbuf[MYSQL_ERRMSG_SIZE]; - char* query = 0; - MYSQL_RES *result; - MYSQL_ROW row; - - if (!RunQuery(query,MakeAnyLenString(&query, "select `msgid`, `body`, `to` from `mail` " - "where `charid`=%i and `msgid`=%i", CharacterID, MessageNumber), errbuf, &result)){ - safe_delete_array(query); - - return ; - } - - safe_delete_array(query); - - if (mysql_num_rows(result) != 1) { - - mysql_free_result(result); - + std::string query = StringFormat("SELECT `msgid`, `body`, `to` FROM `mail` " + "WHERE `charid`=%i AND `msgid`=%i", characterID, messageNumber); + auto results = QueryDatabase(query); + if (!results.Success()) return; - } - row = mysql_fetch_row(result); - _log(UCS__TRACE, "Message: %i body (%i bytes)", MessageNumber, strlen(row[1])); + if (results.RowCount() != 1) + return; - int PacketLength = 12 + strlen(row[0]) + strlen(row[1]) + strlen(row[2]); + auto row = results.begin(); - EQApplicationPacket *outapp = new EQApplicationPacket(OP_MailSendBody,PacketLength); + _log(UCS__TRACE, "Message: %i body (%i bytes)", messageNumber, strlen(row[1])); - char *PacketBuffer = (char *)outapp->pBuffer; + int packetLength = 12 + strlen(row[0]) + strlen(row[1]) + strlen(row[2]); - VARSTRUCT_ENCODE_INTSTRING(PacketBuffer, c->GetMailBoxNumber()); - VARSTRUCT_ENCODE_STRING(PacketBuffer,row[0]); - VARSTRUCT_ENCODE_STRING(PacketBuffer,row[1]); - VARSTRUCT_ENCODE_STRING(PacketBuffer,"1"); - VARSTRUCT_ENCODE_TYPE(uint8, PacketBuffer, 0); - VARSTRUCT_ENCODE_TYPE(uint8, PacketBuffer, 0x0a); - VARSTRUCT_ENCODE_STRING(PacketBuffer, "TO:"); PacketBuffer--; - VARSTRUCT_ENCODE_STRING(PacketBuffer, row[2]); PacketBuffer--; // Overwrite the null terminator - VARSTRUCT_ENCODE_TYPE(uint8, PacketBuffer, 0x0a); + EQApplicationPacket *outapp = new EQApplicationPacket(OP_MailSendBody,packetLength); - mysql_free_result(result); + char *packetBuffer = (char *)outapp->pBuffer; + + VARSTRUCT_ENCODE_INTSTRING(packetBuffer, client->GetMailBoxNumber()); + VARSTRUCT_ENCODE_STRING(packetBuffer,row[0]); + VARSTRUCT_ENCODE_STRING(packetBuffer,row[1]); + VARSTRUCT_ENCODE_STRING(packetBuffer,"1"); + VARSTRUCT_ENCODE_TYPE(uint8, packetBuffer, 0); + VARSTRUCT_ENCODE_TYPE(uint8, packetBuffer, 0x0a); + VARSTRUCT_ENCODE_STRING(packetBuffer, "TO:"); + packetBuffer--; + VARSTRUCT_ENCODE_STRING(packetBuffer, row[2]); + packetBuffer--; // Overwrite the null terminator + VARSTRUCT_ENCODE_TYPE(uint8, packetBuffer, 0x0a); _pkt(UCS__PACKETS, outapp); - c->QueuePacket(outapp); + client->QueuePacket(outapp); safe_delete(outapp); - - } -bool Database::SendMail(std::string Recipient, std::string From, std::string Subject, std::string Body, std::string RecipientsString) { +bool Database::SendMail(std::string recipient, std::string from, std::string subject, std::string body, std::string recipientsString) { - int CharacterID; + int characterID; + std::string characterName; - std::string CharacterName; + auto lastPeriod = recipient.find_last_of("."); - //printf("Database::SendMail(%s, %s, %s)\n", Recipient.c_str(), From.c_str(), Subject.c_str()); - - std::string::size_type LastPeriod = Recipient.find_last_of("."); - - if(LastPeriod == std::string::npos) - CharacterName = Recipient; + if(lastPeriod == std::string::npos) + characterName = recipient; else - CharacterName = Recipient.substr(LastPeriod+1); + characterName = recipient.substr(lastPeriod+1); - CharacterName[0] = toupper(CharacterName[0]); + characterName[0] = toupper(characterName[0]); - for(unsigned int i = 1; i < CharacterName.length(); i++) - CharacterName[i] = tolower(CharacterName[i]); + for(unsigned int i = 1; i < characterName.length(); i++) + characterName[i] = tolower(characterName[i]); - CharacterID = FindCharacter(CharacterName.c_str()); + characterID = FindCharacter(characterName.c_str()); - _log(UCS__TRACE, "SendMail: CharacterID for recipient %s is %i", CharacterName.c_str(), CharacterID); + _log(UCS__TRACE, "SendMail: CharacterID for recipient %s is %i", characterName.c_str(), characterID); - if(CharacterID <= 0) return false; + if(characterID <= 0) + return false; - char errbuf[MYSQL_ERRMSG_SIZE]; - char* query = 0; + char *escSubject = new char[subject.length() * 2 + 1]; + char *escBody = new char[body.length() * 2 + 1]; - char *EscSubject = new char[Subject.length() * 2 + 1]; - char *EscBody = new char[Body.length() * 2 + 1]; + DoEscapeString(escSubject, subject.c_str(), subject.length()); + DoEscapeString(escBody, body.c_str(), body.length()); - DoEscapeString(EscSubject, Subject.c_str(), Subject.length()); - DoEscapeString(EscBody, Body.c_str(), Body.length()); - - const char *MailQuery="INSERT INTO `mail` (`charid`, `timestamp`, `from`, `subject`, `body`, `to`, `status`) " - "VALUES ('%i', %i, '%s', '%s', '%s', '%s', %i)"; - - uint32 LastMsgID; - - int Now = time(nullptr); // time returns a 64 bit int on Windows at least, which vsnprintf doesn't like. - - if(!RunQuery(query, MakeAnyLenString(&query, MailQuery, CharacterID, Now, From.c_str(), EscSubject, EscBody, - RecipientsString.c_str(), 1), errbuf, 0, 0, &LastMsgID)) { - - _log(UCS__ERROR, "SendMail: Query %s failed with error %s", query, errbuf); - - safe_delete_array(EscSubject); - safe_delete_array(EscBody); - safe_delete_array(query); + int now = time(nullptr); // time returns a 64 bit int on Windows at least, which vsnprintf doesn't like. + std::string query = StringFormat("INSERT INTO `mail` " + "(`charid`, `timestamp`, `from`, `subject`, `body`, `to`, `status`) " + "VALUES ('%i', %i, '%s', '%s', '%s', '%s', %i)", + characterID, now, from.c_str(), escSubject, escBody, + recipientsString.c_str(), 1); + safe_delete_array(escSubject); + safe_delete_array(escBody); + auto results = QueryDatabase(query); + if(!results.Success()) { + _log(UCS__ERROR, "SendMail: Query %s failed with error %s", query.c_str(), results.ErrorMessage().c_str()); return false; } - _log(UCS__TRACE, "MessageID %i generated, from %s, to %s", LastMsgID, From.c_str(), Recipient.c_str()); + _log(UCS__TRACE, "MessageID %i generated, from %s, to %s", results.LastInsertedID(), from.c_str(), recipient.c_str()); - safe_delete_array(EscSubject); - safe_delete_array(EscBody); - safe_delete_array(query); - Client *c = CL->IsCharacterOnline(CharacterName); + Client *client = CL->IsCharacterOnline(characterName); - if(c) { - std::string FQN = GetMailPrefix() + From; - - c->SendNotification(c->GetMailBoxNumber(CharacterName), Subject, FQN, LastMsgID); + if(client) { + std::string FQN = GetMailPrefix() + from; + client->SendNotification(client->GetMailBoxNumber(characterName), subject, FQN, results.LastInsertedID()); } MailMessagesSent++; @@ -614,156 +485,122 @@ bool Database::SendMail(std::string Recipient, std::string From, std::string Sub return true; } -void Database::SetMessageStatus(int MessageNumber, int Status) { +void Database::SetMessageStatus(int messageNumber, int status) { - _log(UCS__TRACE, "SetMessageStatus %i %i", MessageNumber, Status); + _log(UCS__TRACE, "SetMessageStatus %i %i", messageNumber, status); - char errbuf[MYSQL_ERRMSG_SIZE]; - char *query = 0; + if(status == 0) { + std::string query = StringFormat("DELETE FROM `mail` WHERE `msgid` = %i", messageNumber); + auto results = QueryDatabase(query); + return; + } - if(Status == 0) - RunQuery(query, MakeAnyLenString(&query, "delete from `mail` where `msgid`=%i", MessageNumber), errbuf); - else if (!RunQuery(query, MakeAnyLenString(&query, "update `mail` set `status`=%i where `msgid`=%i", Status, MessageNumber), errbuf)) { + std::string query = StringFormat("UPDATE `mail` SET `status` = %i WHERE `msgid`=%i", status, messageNumber); + auto results = QueryDatabase(query); + if (!results.Success()) + _log(UCS__ERROR, "Error updating status %s, %s", query.c_str(), results.ErrorMessage().c_str()); - _log(UCS__ERROR, "Error updating status %s, %s", query, errbuf); - - } - - safe_delete_array(query); } void Database::ExpireMail() { _log(UCS__INIT, "Expiring mail..."); - char errbuf[MYSQL_ERRMSG_SIZE]; - char* query = 0; - MYSQL_RES *result; - MYSQL_ROW row; - - uint32 AffectedRows; - - if (!RunQuery(query,MakeAnyLenString(&query, "select COUNT(*) from `mail` "),errbuf,&result)){ - _log(UCS__ERROR, "Unable to get message count from database. %s %s", query, errbuf); - safe_delete_array(query); - return ; + std::string query = "SELECT COUNT(*) FROM `mail`"; + auto results = QueryDatabase(query); + if (!results.Success()) { + _log(UCS__ERROR, "Unable to get message count from database. %s %s", query.c_str(), results.ErrorMessage().c_str()); + return; } - safe_delete_array(query); - row = mysql_fetch_row(result); + auto row = results.begin(); _log(UCS__INIT, "There are %s messages in the database.", row[0]); - mysql_free_result(result); - // Expire Trash if(RuleI(Mail, ExpireTrash) >= 0) { - if(RunQuery(query, MakeAnyLenString(&query, "delete from `mail` where `status`=4 and `timestamp` < %i", - time(nullptr) - RuleI(Mail, ExpireTrash)), errbuf, 0, &AffectedRows)) { - _log(UCS__INIT, "Expired %i trash messages.", AffectedRows); - } - else { - _log(UCS__ERROR, "Error expiring trash messages, %s %s", query, errbuf); - } - safe_delete_array(query); + query = StringFormat("DELETE FROM `mail` WHERE `status`=4 AND `timestamp` < %i", + time(nullptr) - RuleI(Mail, ExpireTrash)); + results = QueryDatabase(query); + if(!results.Success()) + _log(UCS__ERROR, "Error expiring trash messages, %s %s", query.c_str(), results.ErrorMessage().c_str()); + else + _log(UCS__INIT, "Expired %i trash messages.", results.RowsAffected()); + } + // Expire Read if(RuleI(Mail, ExpireRead) >= 0) { - if(RunQuery(query, MakeAnyLenString(&query, "delete from `mail` where `status`=3 and `timestamp` < %i", - time(nullptr) - RuleI(Mail, ExpireRead)), errbuf, 0, &AffectedRows)) { - _log(UCS__INIT, "Expired %i read messages.", AffectedRows); - } - else { - _log(UCS__ERROR, "Error expiring read messages, %s %s", query, errbuf); - } - safe_delete_array(query); + query = StringFormat("DELETE FROM `mail` WHERE `status` = 3 AND `timestamp` < %i", + time(nullptr) - RuleI(Mail, ExpireRead)); + results = QueryDatabase(query); + if(!results.Success()) + _log(UCS__INIT, "Expired %i read messages.", results.RowsAffected()); + else + _log(UCS__ERROR, "Error expiring read messages, %s %s", query.c_str(), results.ErrorMessage().c_str()); } + // Expire Unread if(RuleI(Mail, ExpireUnread) >= 0) { - if(RunQuery(query, MakeAnyLenString(&query, "delete from `mail` where `status`=1 and `timestamp` < %i", - time(nullptr) - RuleI(Mail, ExpireUnread)), errbuf, 0, &AffectedRows)) { - _log(UCS__INIT, "Expired %i unread messages.", AffectedRows); - } - else { - _log(UCS__ERROR, "Error expiring unread messages, %s %s", query, errbuf); - } - safe_delete_array(query); + query = StringFormat("DELETE FROM `mail` WHERE `status`=1 AND `timestamp` < %i", + time(nullptr) - RuleI(Mail, ExpireUnread)); + results = QueryDatabase(query); + if(!results.Success()) + _log(UCS__INIT, "Expired %i unread messages.", results.RowsAffected()); + else + _log(UCS__ERROR, "Error expiring unread messages, %s %s", query.c_str(), results.ErrorMessage().c_str()); } } -void Database::AddFriendOrIgnore(int CharID, int Type, std::string Name) { +void Database::AddFriendOrIgnore(int charID, int type, std::string name) { - const char *FriendsQuery="INSERT INTO `friends` (`charid`, `type`, `name`) VALUES ('%i', %i, '%s')"; - - char errbuf[MYSQL_ERRMSG_SIZE]; - char* query = 0; - - - if(!RunQuery(query, MakeAnyLenString(&query, FriendsQuery, CharID, Type, CapitaliseName(Name).c_str()), errbuf, 0, 0)) - _log(UCS__ERROR, "Error adding friend/ignore, query was %s : %s", query, errbuf); + std::string query = StringFormat("INSERT INTO `friends` (`charid`, `type`, `name`) " + "VALUES('%i', %i, '%s')", + charID, type, CapitaliseName(name).c_str()); + auto results = QueryDatabase(query); + if(!results.Success()) + _log(UCS__ERROR, "Error adding friend/ignore, query was %s : %s", query.c_str(), results.ErrorMessage().c_str()); else - _log(UCS__TRACE, "Wrote Friend/Ignore entry for charid %i, type %i, name %s to database.", - CharID, Type, Name.c_str()); + _log(UCS__TRACE, "Wrote Friend/Ignore entry for charid %i, type %i, name %s to database.", charID, type, name.c_str()); - - safe_delete_array(query); } -void Database::RemoveFriendOrIgnore(int CharID, int Type, std::string Name) { +void Database::RemoveFriendOrIgnore(int charID, int type, std::string name) { - const char *FriendsQuery="DELETE FROM `friends` WHERE `charid`=%i AND `type`=%i and `name`='%s'"; - - char errbuf[MYSQL_ERRMSG_SIZE]; - char* query = 0; - - if(!RunQuery(query, MakeAnyLenString(&query, FriendsQuery, CharID, Type, CapitaliseName(Name).c_str()), errbuf, 0, 0)) - _log(UCS__ERROR, "Error removing friend/ignore, query was %s", query); + std::string query = StringFormat("DELETE FROM `friends` WHERE `charid` = %i " + "AND `type` = %i AND `name` = '%s'", + charID, type, CapitaliseName(name).c_str()); + auto results = QueryDatabase(query); + if(!results.Success()) + _log(UCS__ERROR, "Error removing friend/ignore, query was %s", query.c_str()); else - _log(UCS__TRACE, "Removed Friend/Ignore entry for charid %i, type %i, name %s from database.", - CharID, Type, Name.c_str()); + _log(UCS__TRACE, "Removed Friend/Ignore entry for charid %i, type %i, name %s from database.", charID, type, name.c_str()); - - safe_delete_array(query); } -void Database::GetFriendsAndIgnore(int CharID, std::vector &Friends, std::vector &Ignorees) { +void Database::GetFriendsAndIgnore(int charID, std::vector &friends, std::vector &ignorees) { - char errbuf[MYSQL_ERRMSG_SIZE]; - char* query = 0; - MYSQL_RES *result; - MYSQL_ROW row; - - const char *FriendsQuery="select `type`, `name` from `friends` WHERE `charid`=%i"; - - if (!RunQuery(query,MakeAnyLenString(&query, FriendsQuery, CharID),errbuf,&result)){ - - _log(UCS__ERROR, "GetFriendsAndIgnore query error %s, %s", query, errbuf); - - safe_delete_array(query); - - return ; + std::string query = StringFormat("select `type`, `name` FROM `friends` WHERE `charid`=%i", charID); + auto results = QueryDatabase(query); + if (!results.Success()) { + _log(UCS__ERROR, "GetFriendsAndIgnore query error %s, %s", query.c_str(), results.ErrorMessage().c_str()); + return; } - safe_delete_array(query); - while((row = mysql_fetch_row(result))) { - - std::string Name = row[1]; + for (auto row = results.begin(); row != results.end(); ++row) { + std::string name = row[1]; if(atoi(row[0]) == 0) { - Ignorees.push_back(Name); - _log(UCS__TRACE, "Added Ignoree from DB %s", Name.c_str()); - } - else - { - Friends.push_back(Name); - _log(UCS__TRACE, "Added Friend from DB %s", Name.c_str()); + ignorees.push_back(name); + _log(UCS__TRACE, "Added Ignoree from DB %s", name.c_str()); + continue; } + + friends.push_back(name); + _log(UCS__TRACE, "Added Friend from DB %s", name.c_str()); } - mysql_free_result(result); - - return; } diff --git a/utils/sql/git/queryserv/required/08_23_2014_player_events_and_player_aa_rate_hourly.sql b/utils/sql/git/queryserv/required/08_23_2014_player_events_and_player_aa_rate_hourly.sql new file mode 100644 index 000000000..2d5cbfa17 --- /dev/null +++ b/utils/sql/git/queryserv/required/08_23_2014_player_events_and_player_aa_rate_hourly.sql @@ -0,0 +1,23 @@ +-- ---------------------------- +-- Table structure for qs_player_events +-- ---------------------------- +DROP TABLE IF EXISTS `qs_player_events`; +CREATE TABLE `qs_player_events` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `char_id` int(11) DEFAULT '0', + `event` int(11) unsigned DEFAULT '0', + `event_desc` varchar(255) DEFAULT NULL, + `time` int(11) unsigned DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; + +-- ---------------------------- +-- Table structure for qs_player_aa_rate_hourly +-- ---------------------------- +DROP TABLE IF EXISTS `qs_player_aa_rate_hourly`; +CREATE TABLE `qs_player_aa_rate_hourly` ( + `char_id` int(11) NOT NULL DEFAULT '0', + `hour_time` int(11) NOT NULL, + `aa_count` varchar(11) DEFAULT NULL, + PRIMARY KEY (`char_id`,`hour_time`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; \ No newline at end of file diff --git a/utils/sql/git/queryserv/required/Complete_QueryServ_Rules_Disabled.sql b/utils/sql/git/queryserv/required/Complete_QueryServ_Rules_Disabled.sql new file mode 100644 index 000000000..65206ce4c --- /dev/null +++ b/utils/sql/git/queryserv/required/Complete_QueryServ_Rules_Disabled.sql @@ -0,0 +1,45 @@ +-- Disable Player Logging for All -- +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogPCCoordinates', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogNPCKills', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogTrades', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogMerchantTransactions', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogDeletes', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogHandins', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogMoves', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogChat', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogKeyringAddition', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogAAPurchases', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogIssuedCommandes', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogMoneyTransactions', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogAlternateCurrencyTransactions', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogTradeSkillEvents', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogPCCoordinates', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogDropItem', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogMerchantTransactions', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogDeletes', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogHandins', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogMoves', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogNPCKills', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogTrades', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogQGlobalUpdate', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogTaskUpdates', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogDeaths', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogZone', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogConnectDisconnect', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogLevels', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogAARate', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogChat', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogDropItem', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogZone', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogDeaths', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogLevels', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogEXPRate', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogAARate', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogQGlobalUpdate', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogTaskUpdates', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogKeyringAddition', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogAAPurchases', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogTradeSkillEvents', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogIssuedCommandes', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogMoneyTransactions', 'false', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogAlternateCurrencyTransactions', 'false', ''); diff --git a/utils/sql/git/queryserv/required/Complete_QueryServ_Rules_Enabled.sql b/utils/sql/git/queryserv/required/Complete_QueryServ_Rules_Enabled.sql new file mode 100644 index 000000000..9426acc8f --- /dev/null +++ b/utils/sql/git/queryserv/required/Complete_QueryServ_Rules_Enabled.sql @@ -0,0 +1,45 @@ +-- Enable Player Logging for All -- +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogPCCoordinates', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogNPCKills', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogTrades', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogMerchantTransactions', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogDeletes', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogHandins', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogMoves', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogChat', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogKeyringAddition', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogAAPurchases', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogIssuedCommandes', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogMoneyTransactions', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogAlternateCurrencyTransactions', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogTradeSkillEvents', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogPCCoordinates', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogDropItem', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogMerchantTransactions', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogDeletes', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogHandins', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogMoves', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogNPCKills', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogTrades', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogQGlobalUpdate', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogTaskUpdates', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogDeaths', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogZone', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogConnectDisconnect', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogLevels', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (9, 'QueryServ:PlayerLogAARate', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogChat', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogDropItem', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogZone', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogDeaths', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogLevels', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogEXPRate', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogAARate', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogQGlobalUpdate', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogTaskUpdates', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogKeyringAddition', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogAAPurchases', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogTradeSkillEvents', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogIssuedCommandes', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogMoneyTransactions', 'true', ''); +REPLACE INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (0, 'QueryServ:PlayerLogAlternateCurrencyTransactions', 'true', ''); diff --git a/utils/sql/git/queryserv/required/Complete_QueryServ_Table_Structures.sql b/utils/sql/git/queryserv/required/Complete_QueryServ_Table_Structures.sql new file mode 100644 index 000000000..40dc41377 --- /dev/null +++ b/utils/sql/git/queryserv/required/Complete_QueryServ_Table_Structures.sql @@ -0,0 +1,247 @@ +-- QS Table Structures -- + +SET FOREIGN_KEY_CHECKS=0; + +-- ---------------------------- +-- Table structure for qs_merchant_transaction_record +-- ---------------------------- +DROP TABLE IF EXISTS `qs_merchant_transaction_record`; +CREATE TABLE `qs_merchant_transaction_record` ( + `transaction_id` int(11) NOT NULL AUTO_INCREMENT, + `time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, + `zone_id` int(11) DEFAULT '0', + `merchant_id` int(11) DEFAULT '0', + `merchant_pp` int(11) DEFAULT '0', + `merchant_gp` int(11) DEFAULT '0', + `merchant_sp` int(11) DEFAULT '0', + `merchant_cp` int(11) DEFAULT '0', + `merchant_items` mediumint(7) DEFAULT '0', + `char_id` int(11) DEFAULT '0', + `char_pp` int(11) DEFAULT '0', + `char_gp` int(11) DEFAULT '0', + `char_sp` int(11) DEFAULT '0', + `char_cp` int(11) DEFAULT '0', + `char_items` mediumint(7) DEFAULT '0', + PRIMARY KEY (`transaction_id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; + +-- ---------------------------- +-- Table structure for qs_merchant_transaction_record_entries +-- ---------------------------- +DROP TABLE IF EXISTS `qs_merchant_transaction_record_entries`; +CREATE TABLE `qs_merchant_transaction_record_entries` ( + `event_id` int(11) DEFAULT '0', + `char_slot` mediumint(7) DEFAULT '0', + `item_id` int(11) DEFAULT '0', + `charges` mediumint(7) DEFAULT '0', + `aug_1` int(11) DEFAULT '0', + `aug_2` int(11) DEFAULT '0', + `aug_3` int(11) DEFAULT '0', + `aug_4` int(11) DEFAULT '0', + `aug_5` int(11) DEFAULT '0' +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- ---------------------------- +-- Table structure for qs_player_aa_rate_hourly +-- ---------------------------- +DROP TABLE IF EXISTS `qs_player_aa_rate_hourly`; +CREATE TABLE `qs_player_aa_rate_hourly` ( + `char_id` int(11) NOT NULL DEFAULT '0', + `hour_time` int(11) NOT NULL, + `aa_count` varchar(11) DEFAULT NULL, + PRIMARY KEY (`char_id`,`hour_time`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +-- ---------------------------- +-- Table structure for qs_player_delete_record +-- ---------------------------- +DROP TABLE IF EXISTS `qs_player_delete_record`; +CREATE TABLE `qs_player_delete_record` ( + `delete_id` int(11) NOT NULL AUTO_INCREMENT, + `time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, + `char_id` int(11) DEFAULT '0', + `stack_size` mediumint(7) DEFAULT '0', + `char_items` mediumint(7) DEFAULT '0', + PRIMARY KEY (`delete_id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; + +-- ---------------------------- +-- Table structure for qs_player_delete_record_entries +-- ---------------------------- +DROP TABLE IF EXISTS `qs_player_delete_record_entries`; +CREATE TABLE `qs_player_delete_record_entries` ( + `event_id` int(11) DEFAULT '0', + `char_slot` mediumint(7) DEFAULT '0', + `item_id` int(11) DEFAULT '0', + `charges` mediumint(7) DEFAULT '0', + `aug_1` int(11) DEFAULT '0', + `aug_2` int(11) DEFAULT '0', + `aug_3` int(11) DEFAULT '0', + `aug_4` int(11) DEFAULT '0', + `aug_5` int(11) DEFAULT '0' +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- ---------------------------- +-- Table structure for qs_player_events +-- ---------------------------- +DROP TABLE IF EXISTS `qs_player_events`; +CREATE TABLE `qs_player_events` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `char_id` int(11) DEFAULT '0', + `event` int(11) unsigned DEFAULT '0', + `event_desc` varchar(255) DEFAULT NULL, + `time` int(11) unsigned DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; + +-- ---------------------------- +-- Table structure for qs_player_handin_record +-- ---------------------------- +DROP TABLE IF EXISTS `qs_player_handin_record`; +CREATE TABLE `qs_player_handin_record` ( + `handin_id` int(11) NOT NULL AUTO_INCREMENT, + `time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, + `quest_id` int(11) DEFAULT '0', + `char_id` int(11) DEFAULT '0', + `char_pp` int(11) DEFAULT '0', + `char_gp` int(11) DEFAULT '0', + `char_sp` int(11) DEFAULT '0', + `char_cp` int(11) DEFAULT '0', + `char_items` mediumint(7) DEFAULT '0', + `npc_id` int(11) DEFAULT '0', + `npc_pp` int(11) DEFAULT '0', + `npc_gp` int(11) DEFAULT '0', + `npc_sp` int(11) DEFAULT '0', + `npc_cp` int(11) DEFAULT '0', + `npc_items` mediumint(7) DEFAULT '0', + PRIMARY KEY (`handin_id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; + +-- ---------------------------- +-- Table structure for qs_player_handin_record_entries +-- ---------------------------- +DROP TABLE IF EXISTS `qs_player_handin_record_entries`; +CREATE TABLE `qs_player_handin_record_entries` ( + `event_id` int(11) DEFAULT '0', + `action_type` char(6) DEFAULT 'action', + `char_slot` mediumint(7) DEFAULT '0', + `item_id` int(11) DEFAULT '0', + `charges` mediumint(7) DEFAULT '0', + `aug_1` int(11) DEFAULT '0', + `aug_2` int(11) DEFAULT '0', + `aug_3` int(11) DEFAULT '0', + `aug_4` int(11) DEFAULT '0', + `aug_5` int(11) DEFAULT '0' +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- ---------------------------- +-- Table structure for qs_player_move_record +-- ---------------------------- +DROP TABLE IF EXISTS `qs_player_move_record`; +CREATE TABLE `qs_player_move_record` ( + `move_id` int(11) NOT NULL AUTO_INCREMENT, + `time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, + `char_id` int(11) DEFAULT '0', + `from_slot` mediumint(7) DEFAULT '0', + `to_slot` mediumint(7) DEFAULT '0', + `stack_size` mediumint(7) DEFAULT '0', + `char_items` mediumint(7) DEFAULT '0', + `postaction` tinyint(1) DEFAULT '0', + PRIMARY KEY (`move_id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; + +-- ---------------------------- +-- Table structure for qs_player_move_record_entries +-- ---------------------------- +DROP TABLE IF EXISTS `qs_player_move_record_entries`; +CREATE TABLE `qs_player_move_record_entries` ( + `event_id` int(11) DEFAULT '0', + `from_slot` mediumint(7) DEFAULT '0', + `to_slot` mediumint(7) DEFAULT '0', + `item_id` int(11) DEFAULT '0', + `charges` mediumint(7) DEFAULT '0', + `aug_1` int(11) DEFAULT '0', + `aug_2` int(11) DEFAULT '0', + `aug_3` int(11) DEFAULT '0', + `aug_4` int(11) DEFAULT '0', + `aug_5` int(11) DEFAULT '0' +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- ---------------------------- +-- Table structure for qs_player_npc_kill_record +-- ---------------------------- +DROP TABLE IF EXISTS `qs_player_npc_kill_record`; +CREATE TABLE `qs_player_npc_kill_record` ( + `fight_id` int(11) NOT NULL AUTO_INCREMENT, + `npc_id` int(11) DEFAULT NULL, + `type` int(11) DEFAULT NULL, + `zone_id` int(11) DEFAULT NULL, + `time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`fight_id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; + +-- ---------------------------- +-- Table structure for qs_player_npc_kill_record_entries +-- ---------------------------- +DROP TABLE IF EXISTS `qs_player_npc_kill_record_entries`; +CREATE TABLE `qs_player_npc_kill_record_entries` ( + `event_id` int(11) DEFAULT '0', + `char_id` int(11) DEFAULT '0' +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- ---------------------------- +-- Table structure for qs_player_speech +-- ---------------------------- +DROP TABLE IF EXISTS `qs_player_speech`; +CREATE TABLE `qs_player_speech` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `from` varchar(64) NOT NULL, + `to` varchar(64) NOT NULL, + `message` varchar(256) NOT NULL, + `minstatus` smallint(5) NOT NULL, + `guilddbid` int(11) NOT NULL, + `type` tinyint(3) NOT NULL, + `timerecorded` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; + +-- ---------------------------- +-- Table structure for qs_player_trade_record +-- ---------------------------- +DROP TABLE IF EXISTS `qs_player_trade_record`; +CREATE TABLE `qs_player_trade_record` ( + `trade_id` int(11) NOT NULL AUTO_INCREMENT, + `time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, + `char1_id` int(11) DEFAULT '0', + `char1_pp` int(11) DEFAULT '0', + `char1_gp` int(11) DEFAULT '0', + `char1_sp` int(11) DEFAULT '0', + `char1_cp` int(11) DEFAULT '0', + `char1_items` mediumint(7) DEFAULT '0', + `char2_id` int(11) DEFAULT '0', + `char2_pp` int(11) DEFAULT '0', + `char2_gp` int(11) DEFAULT '0', + `char2_sp` int(11) DEFAULT '0', + `char2_cp` int(11) DEFAULT '0', + `char2_items` mediumint(7) DEFAULT '0', + PRIMARY KEY (`trade_id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; + +-- ---------------------------- +-- Table structure for qs_player_trade_record_entries +-- ---------------------------- +DROP TABLE IF EXISTS `qs_player_trade_record_entries`; +CREATE TABLE `qs_player_trade_record_entries` ( + `event_id` int(11) DEFAULT '0', + `from_id` int(11) DEFAULT '0', + `from_slot` mediumint(7) DEFAULT '0', + `to_id` int(11) DEFAULT '0', + `to_slot` mediumint(7) DEFAULT '0', + `item_id` int(11) DEFAULT '0', + `charges` mediumint(7) DEFAULT '0', + `aug_1` int(11) DEFAULT '0', + `aug_2` int(11) DEFAULT '0', + `aug_3` int(11) DEFAULT '0', + `aug_4` int(11) DEFAULT '0', + `aug_5` int(11) DEFAULT '0' +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/utils/sql/git/required/2014_08_24_character_lookup.sql b/utils/sql/git/required/2014_08_24_character_lookup.sql new file mode 100644 index 000000000..414dbbdcb --- /dev/null +++ b/utils/sql/git/required/2014_08_24_character_lookup.sql @@ -0,0 +1,33 @@ +-- chracter_lookup table structure -- + +CREATE TABLE `character_lookup` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `account_id` int(11) NOT NULL DEFAULT '0', + `name` varchar(64) NOT NULL DEFAULT '', + `timelaston` int(11) unsigned DEFAULT '0', + `x` float NOT NULL DEFAULT '0', + `y` float NOT NULL DEFAULT '0', + `z` float NOT NULL DEFAULT '0', + `zonename` varchar(30) NOT NULL DEFAULT '', + `zoneid` smallint(6) NOT NULL DEFAULT '0', + `instanceid` smallint(5) unsigned NOT NULL DEFAULT '0', + `pktime` int(8) NOT NULL DEFAULT '0', + `groupid` int(10) unsigned NOT NULL DEFAULT '0', + `class` tinyint(4) NOT NULL DEFAULT '0', + `level` mediumint(8) unsigned NOT NULL DEFAULT '0', + `lfp` tinyint(1) unsigned NOT NULL DEFAULT '0', + `lfg` tinyint(1) unsigned NOT NULL DEFAULT '0', + `mailkey` char(16) NOT NULL, + `xtargets` tinyint(3) unsigned NOT NULL DEFAULT '5', + `firstlogon` tinyint(3) NOT NULL DEFAULT '0', + `inspectmessage` varchar(256) NOT NULL DEFAULT '', + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`), + KEY `account_id` (`account_id`) +) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; + +-- Initial population of the character_lookup table -- + +INSERT INTO `character_lookup` (id, account_id, `name`, timelaston, x, y, z, zonename, zoneid, instanceid, pktime, groupid, class, `level`, lfp, lfg, mailkey, xtargets, firstlogon, inspectmessage) +SELECT id, account_id, `name`, timelaston, x, y, z, zonename, zoneid, instanceid, pktime, groupid, class, `level`, lfp, lfg, mailkey, xtargets, firstlogon, inspectmessage +FROM `character_`; \ No newline at end of file diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index a719deecf..7c4f4f963 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -1268,7 +1268,7 @@ bool ZoneServer::Process() { UCSLink.SendPacket(pack); break; } - + case ServerOP_QSSendQuery: case ServerOP_QueryServGeneric: case ServerOP_Speech: case ServerOP_QSPlayerLogTrades: @@ -1296,7 +1296,7 @@ bool ZoneServer::Process() { QSLink.SendPacket(pack); break; } - case ServerOP_QSMerchantLogTransactions: + case ServerOP_QSPlayerLogMerchantTransactions: { QSLink.SendPacket(pack); break; diff --git a/zone/CMakeLists.txt b/zone/CMakeLists.txt index 8459fb79a..0b6ebc10a 100644 --- a/zone/CMakeLists.txt +++ b/zone/CMakeLists.txt @@ -16,7 +16,7 @@ SET(zone_sources command.cpp corpse.cpp doors.cpp - effects.cpp + effects.cpp embparser.cpp embparser_api.cpp embperl.cpp @@ -93,6 +93,7 @@ SET(zone_sources petitions.cpp pets.cpp qglobals.cpp + queryserv.cpp questmgr.cpp quest_parser_collection.cpp raids.cpp @@ -184,6 +185,8 @@ SET(zone_headers pets.h qglobals.h quest_interface.h + queryserv.h + quest_interface.h questmgr.h quest_parser_collection.h raid.h diff --git a/zone/aa.cpp b/zone/aa.cpp index 1bf2ba690..2964dc6f3 100644 --- a/zone/aa.cpp +++ b/zone/aa.cpp @@ -38,6 +38,9 @@ Copyright (C) 2001-2004 EQEMu Development Team (http://eqemulator.net) #include "../common/logsys.h" #include "zonedb.h" #include "string_ids.h" +#include "queryserv.h" + +extern QueryServ* QServ; //static data arrays, really not big enough to warrant shared mem. AA_DBAction AA_Actions[aaHighestID][MAX_AA_ACTION_RANKS]; //[aaid][rank] @@ -1039,16 +1042,16 @@ void Client::BuyAA(AA_Action* action) else real_cost = aa2->cost + (aa2->cost_inc * cur_level); - if(m_pp.aapoints >= real_cost && cur_level < aa2->max_level) { - SetAA(aa2->id, cur_level+1); + if (m_pp.aapoints >= real_cost && cur_level < aa2->max_level) { + SetAA(aa2->id, cur_level + 1); - mlog(AA__MESSAGE, "Set AA %d to level %d", aa2->id, cur_level+1); + mlog(AA__MESSAGE, "Set AA %d to level %d", aa2->id, cur_level + 1); m_pp.aapoints -= real_cost; Save(); if ((RuleB(AA, Stacking) && (GetClientVersionBit() >= 4) && (aa2->hotkey_sid == 4294967295u)) - && ((aa2->max_level == (cur_level+1)) && aa2->sof_next_id)){ + && ((aa2->max_level == (cur_level + 1)) && aa2->sof_next_id)){ SendAA(aa2->id); SendAA(aa2->sof_next_id); } @@ -1059,10 +1062,28 @@ void Client::BuyAA(AA_Action* action) //we are building these messages ourself instead of using the stringID to work around patch discrepencies //these are AA_GAIN_ABILITY (410) & AA_IMPROVE (411), respectively, in both Titanium & SoF. not sure about 6.2 - if(cur_level<1) - Message(15,"You have gained the ability \"%s\" at a cost of %d ability %s.", aa2->name, real_cost, (real_cost>1)?"points":"point"); - else - Message(15,"You have improved %s %d at a cost of %d ability %s.", aa2->name, cur_level+1, real_cost, (real_cost>1)?"points":"point"); + + /* Initial purchase of an AA ability */ + if (cur_level < 1){ + Message(15, "You have gained the ability \"%s\" at a cost of %d ability %s.", aa2->name, real_cost, (real_cost>1) ? "points" : "point"); + + /* QS: Player_Log_AA_Purchases */ + if (RuleB(QueryServ, PlayerLogAAPurchases)){ + std::string event_desc = StringFormat("Initial AA Purchase :: aa_name:%s aa_id:%i at cost:%i in zoneid:%i instid:%i", aa2->name, aa2->id, real_cost, this->GetZoneID(), this->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_AA_Purchases, this->CharacterID(), event_desc); + } + } + /* Ranked purchase of an AA ability */ + else{ + Message(15, "You have improved %s %d at a cost of %d ability %s.", aa2->name, cur_level + 1, real_cost, (real_cost > 1) ? "points" : "point"); + + /* QS: Player_Log_AA_Purchases */ + if (RuleB(QueryServ, PlayerLogAAPurchases)){ + std::string event_desc = StringFormat("Ranked AA Purchase :: aa_name:%s aa_id:%i at cost:%i in zoneid:%i instid:%i", aa2->name, aa2->id, real_cost, this->GetZoneID(), this->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_AA_Purchases, this->CharacterID(), event_desc); + } + } + SendAAStats(); @@ -1816,7 +1837,7 @@ SendAA_Struct* ZoneDatabase::GetAASkillVars(uint32 skill_id) } query = StringFormat("SELECT a.cost, a.max_level, a.hotkey_sid, a.hotkey_sid2, a.title_sid, a.desc_sid, a.type, " - "COALESCE (" //So we can return 0 if it's null. + "COALESCE(" //So we can return 0 if it's null. "(" // this is our derived table that has the row # // that we can SELECT from, because the client is stupid. "SELECT p.prereq_index_num " diff --git a/zone/aggro.cpp b/zone/aggro.cpp index d55dab4fd..30ef4087d 100644 --- a/zone/aggro.cpp +++ b/zone/aggro.cpp @@ -873,7 +873,34 @@ bool Mob::CombatRange(Mob* other) if (size_mod > 10000) size_mod = size_mod / 7; - if (DistNoRoot(*other) <= size_mod) + float _DistNoRoot = DistNoRoot(*other); + + if (GetSpecialAbility(NPC_CHASE_DISTANCE)){ + + bool DoLoSCheck = true; + float max_dist = static_cast(GetSpecialAbilityParam(NPC_CHASE_DISTANCE, 0)); + float min_dist = static_cast(GetSpecialAbilityParam(NPC_CHASE_DISTANCE, 1)); + + if (GetSpecialAbilityParam(NPC_CHASE_DISTANCE, 2)); + DoLoSCheck = false; //Ignore line of sight check + + if (max_dist == 1) + max_dist = 250.0f; //Default it to 250 if you forget to put a value + + max_dist = max_dist * max_dist; + + if (!min_dist) + min_dist = size_mod; //Default to melee range + else + min_dist = min_dist * min_dist; + + if ((DoLoSCheck && CheckLastLosState()) && (_DistNoRoot >= min_dist && _DistNoRoot <= max_dist)) + SetPseudoRoot(true); + else + SetPseudoRoot(false); + } + + if (_DistNoRoot <= size_mod) { return true; } @@ -887,6 +914,8 @@ bool Mob::CheckLosFN(Mob* other) { if(other) Result = CheckLosFN(other->GetX(), other->GetY(), other->GetZ(), other->GetSize()); + SetLastLosState(Result); + return Result; } diff --git a/zone/attack.cpp b/zone/attack.cpp index dd14b1770..5abba347b 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -42,6 +42,9 @@ #include "quest_parser_collection.h" #include "water_map.h" #include "worldserver.h" +#include "queryserv.h" + +extern QueryServ* QServ; extern WorldServer worldserver; #ifdef _WINDOWS @@ -1455,14 +1458,14 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes att int exploss = 0; mlog(COMBAT__HITS, "Fatal blow dealt by %s with %d damage, spell %d, skill %d", killerMob ? killerMob->GetName() : "Unknown", damage, spell, attack_skill); - // - // #1: Send death packet to everyone - // + /* + #1: Send death packet to everyone + */ uint8 killed_level = GetLevel(); SendLogoutPackets(); - //make our become corpse packet, and queue to ourself before OP_Death. + /* Make self become corpse packet */ EQApplicationPacket app2(OP_BecomeCorpse, sizeof(BecomeCorpse_Struct)); BecomeCorpse_Struct* bc = (BecomeCorpse_Struct*)app2.pBuffer; bc->spawn_id = GetID(); @@ -1471,7 +1474,7 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes att bc->z = GetZ(); QueuePacket(&app2); - // make death packet + /* Make Death Packet */ EQApplicationPacket app(OP_Death, sizeof(Death_Struct)); Death_Struct* d = (Death_Struct*)app.pBuffer; d->spawn_id = GetID(); @@ -1484,9 +1487,9 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes att app.priority = 6; entity_list.QueueClients(this, &app); - // - // #2: figure out things that affect the player dying and mark them dead - // + /* + #2: figure out things that affect the player dying and mark them dead + */ InterruptSpell(); SetPet(0); @@ -1541,9 +1544,9 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes att //remove ourself from all proximities ClearAllProximities(); - // - // #3: exp loss and corpse generation - // + /* + #3: exp loss and corpse generation + */ // figure out if they should lose exp if(RuleB(Character, UseDeathExpLossMult)){ @@ -1659,27 +1662,21 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes att LeftCorpse = true; } - -// if(!IsLD())//Todo: make it so an LDed client leaves corpse if its enabled -// MakeCorpse(exploss); } else { BuffFadeDetrimental(); } - // - // Finally, send em home - // + /* + Finally, send em home - // we change the mob variables, not pp directly, because Save() will copy - // from these and overwrite what we set in pp anyway - // + We change the mob variables, not pp directly, because Save() will copy + from these and overwrite what we set in pp anyway + */ if(LeftCorpse && (GetClientVersionBit() & BIT_SoFAndLater) && RuleB(Character, RespawnFromHover)) { - ClearDraggedCorpses(); - - RespawnFromHoverTimer.Start(RuleI(Character, RespawnFromHoverTimer) * 1000); - + ClearDraggedCorpses(); + RespawnFromHoverTimer.Start(RuleI(Character, RespawnFromHoverTimer) * 1000); SendRespawnBinds(); } else @@ -1696,17 +1693,22 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes att if(r) r->MemberZoned(this); - dead_timer.Start(5000, true); - + dead_timer.Start(5000, true); m_pp.zone_id = m_pp.binds[0].zoneId; m_pp.zoneInstance = 0; - database.MoveCharacterToZone(this->CharacterID(), database.GetZoneName(m_pp.zone_id)); - - Save(); - + database.MoveCharacterToZone(this->CharacterID(), database.GetZoneName(m_pp.zone_id)); + Save(); GoToDeath(); } + /* QS: PlayerLogDeaths */ + if (RuleB(QueryServ, PlayerLogDeaths)){ + const char * killer_name = ""; + if (killerMob && killerMob->GetCleanName()){ killer_name = killerMob->GetCleanName(); } + std::string event_desc = StringFormat("Died in zoneid:%i instid:%i by '%s', spellid:%i, damage:%i", this->GetZoneID(), this->GetInstanceID(), killer_name, spell, damage); + QServ->PlayerLogEvent(Player_Log_Deaths, this->CharacterID(), event_desc); + } + parse->EventPlayer(EVENT_DEATH_COMPLETE, this, buffer, 0); return true; } diff --git a/zone/client.cpp b/zone/client.cpp index b1ed375de..1aabccce8 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -62,8 +62,9 @@ extern volatile bool RunLoops; #include "client_logs.h" #include "guild_mgr.h" #include "quest_parser_collection.h" +#include "queryserv.h" - +extern QueryServ* QServ; extern EntityList entity_list; extern Zone* zone; extern volatile bool ZoneLoaded; @@ -324,7 +325,7 @@ Client::Client(EQStreamInterface* ieqs) initial_respawn_selection = 0; alternate_currency_loaded = false; - + EngagedRaidTarget = false; SavedRaidRestTimer = 0; } @@ -629,6 +630,9 @@ bool Client::Save(uint8 iCommitNow) { return false; } + /* Mirror Character Data */ + database.StoreCharacterLookup(this->CharacterID()); + return true; } @@ -805,8 +809,8 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s } } - - if(RuleB(QueryServ, PlayerChatLogging)) { + /* Logs Player Chat */ + if (RuleB(QueryServ, PlayerLogChat)) { ServerPacket* pack = new ServerPacket(ServerOP_Speech, sizeof(Server_Speech_Struct) + strlen(message) + 1); Server_Speech_Struct* sem = (Server_Speech_Struct*) pack->pBuffer; @@ -841,7 +845,7 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s switch(chan_num) { - case 0: { // GuildChat + case 0: { /* Guild Chat */ if (!IsInAGuild()) Message_StringID(MT_DefaultText, GUILD_NOT_MEMBER2); //You are not a member of any guild. else if (!guild_mgr.CheckPermission(GuildID(), GuildRank(), GUILD_SPEAK)) @@ -850,7 +854,7 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s Message(0, "Error: World server disconnected"); break; } - case 2: { // GroupChat + case 2: { /* Group Chat */ Raid* raid = entity_list.GetRaidByClient(this); if(raid) { raid->RaidGroupSay((const char*) message, this); @@ -863,14 +867,14 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s } break; } - case 15: { //raid say + case 15: { /* Raid Say */ Raid* raid = entity_list.GetRaidByClient(this); if(raid){ raid->RaidSay((const char*) message, this); } break; } - case 3: { // Shout + case 3: { /* Shout */ Mob *sender = this; if (GetPet() && GetPet()->FindType(SE_VoiceGraft)) sender = GetPet(); @@ -878,7 +882,7 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s entity_list.ChannelMessage(sender, chan_num, language, lang_skill, message); break; } - case 4: { // Auction + case 4: { /* Auction */ if(RuleB(Chat, ServerWideAuction)) { if(!global_channel_timer.Check()) @@ -917,7 +921,7 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s } break; } - case 5: { // OOC + case 5: { /* OOC */ if(RuleB(Chat, ServerWideOOC)) { if(!global_channel_timer.Check()) @@ -964,15 +968,15 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s } break; } - case 6: // Broadcast - case 11: { // GMSay + case 6: /* Broadcast */ + case 11: { /* GM Say */ if (!(admin >= 80)) Message(0, "Error: Only GMs can use this channel"); else if (!worldserver.SendChannelMessage(this, targetname, chan_num, 0, language, message)) Message(0, "Error: World server disconnected"); break; } - case 7: { // Tell + case 7: { /* Tell */ if(!global_channel_timer.Check()) { if(strlen(targetname) == 0) @@ -1020,7 +1024,7 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s Message(0, "Error: World server disconnected"); break; } - case 8: { // /say + case 8: { /* Say */ if(message[0] == COMMAND_CHAR) { if(command_dispatch(this, message) == -2) { if(parse->PlayerHasQuestSub(EVENT_COMMAND)) { @@ -1029,7 +1033,7 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s Message(13, "Command '%s' not recognized.", message); } } else { - if(!RuleB(Chat, SuppressCommandErrors)) + if(!RuleB(Chat, SuppressCommandErrors)) Message(13, "Command '%s' not recognized.", message); } } @@ -4060,25 +4064,40 @@ void Client::KeyRingList() bool Client::IsDiscovered(uint32 itemid) { - std::string query = StringFormat("SELECT count(*) FROM discovered_items WHERE item_id = '%lu'", itemid); - auto results = database.QueryDatabase(query); - if (!results.Success()) { - std::cerr << "Error in IsDiscovered query '" << query << "' " << results.ErrorMessage() << std::endl; - return false; - } + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; - auto row = results.begin(); - - return atoi(row[0]) != 0; + if (database.RunQuery(query, MakeAnyLenString(&query, "SELECT count(*) FROM discovered_items WHERE item_id = '%lu'", itemid), errbuf, &result)) + { + row = mysql_fetch_row(result); + if (atoi(row[0])) + { + mysql_free_result(result); + safe_delete_array(query); + return true; + } + } + else + { + std::cerr << "Error in IsDiscovered query '" << query << "' " << errbuf << std::endl; + } + mysql_free_result(result); + safe_delete_array(query); + return false; } void Client::DiscoverItem(uint32 itemid) { - std::string query = StringFormat("INSERT INTO discovered_items SET " - "item_id = %lu, char_name = '%s', " - "discovered_date = UNIX_TIMESTAMP(), account_status=%i", - itemid, GetName(), Admin()); - auto results = database.QueryDatabase(query); + char errbuf[MYSQL_ERRMSG_SIZE]; + char* query = 0; + MYSQL_RES *result; + if (database.RunQuery(query,MakeAnyLenString(&query, "INSERT INTO discovered_items SET item_id=%lu, char_name='%s', discovered_date=UNIX_TIMESTAMP(), account_status=%i", itemid, GetName(), Admin()), errbuf, &result)) + { + mysql_free_result(result); + } + safe_delete_array(query); parse->EventPlayer(EVENT_DISCOVER_ITEM, this, "", itemid); } @@ -4315,15 +4334,15 @@ void Client::IncrementAggroCount() { if(!RuleI(Character, RestRegenPercent)) return; - + // If we already had aggro before this method was called, the combat indicator should already be up for SoF clients, // so we don't need to send it again. // if(AggroCount > 1) return; - + // Pause the rest timer - if (AggroCount == 1) + if (AggroCount == 1) SavedRaidRestTimer = rest_timer.GetRemainingTime(); if(GetClientVersion() >= EQClientSoF) { @@ -4368,9 +4387,9 @@ void Client::DecrementAggroCount() { time_until_rest = RuleI(Character, RestRegenTimeToActivate) * 1000; } } - + rest_timer.Start(time_until_rest); - + if(GetClientVersion() >= EQClientSoF) { EQApplicationPacket *outapp = new EQApplicationPacket(OP_RestState, 5); @@ -4475,7 +4494,7 @@ void Client::SendRespawnBinds() int num_options = respawn_options.size(); uint32 PacketLength = 17 + (26 * num_options); //Header size + per-option invariant size - + std::list::iterator itr; RespawnOption* opt; @@ -5261,22 +5280,32 @@ const bool Client::IsMQExemptedArea(uint32 zoneID, float x, float y, float z) co void Client::SendRewards() { std::vector rewards; - std::string query = StringFormat("SELECT reward_id, amount FROM " - "account_rewards WHERE account_id = %i " - "ORDER BY reward_id", AccountID()); - auto results = database.QueryDatabase(query); - if (!results.Success()) { - LogFile->write(EQEMuLog::Error, "Error in Client::SendRewards(): %s (%s)", query.c_str(), results.ErrorMessage().c_str()); + char errbuf[MYSQL_ERRMSG_SIZE]; + char* query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + + if(database.RunQuery(query,MakeAnyLenString(&query,"SELECT reward_id, amount FROM" + " account_rewards WHERE account_id=%i ORDER by reward_id", AccountID()), + errbuf,&result)) + { + while((row = mysql_fetch_row(result))) + { + ClientReward cr; + cr.id = atoi(row[0]); + cr.amount = atoi(row[1]); + rewards.push_back(cr); + } + mysql_free_result(result); + safe_delete_array(query); + } + else + { + LogFile->write(EQEMuLog::Error, "Error in Client::SendRewards(): %s (%s)", query, errbuf); + safe_delete_array(query); return; } - for (auto row = results.begin(); row != results.end(); ++row) { - ClientReward cr; - cr.id = atoi(row[0]); - cr.amount = atoi(row[1]); - rewards.push_back(cr); - } - if(rewards.size() > 0) { EQApplicationPacket *vetapp = new EQApplicationPacket(OP_VetRewardsAvaliable, (sizeof(InternalVeteranReward) * rewards.size())); @@ -5336,54 +5365,88 @@ bool Client::TryReward(uint32 claim_id) } if(free_slot == 0xFFFFFFFF) - return false; - - std::string query = StringFormat("SELECT amount FROM account_rewards " - "WHERE account_id=%i AND reward_id=%i", - AccountID(), claim_id); - auto results = database.QueryDatabase(query); - if (!results.Success()) { - LogFile->write(EQEMuLog::Error, "Error in Client::TryReward(): %s (%s)", query.c_str(), results.ErrorMessage().c_str()); - return false; - } - - if (results.RowCount() == 0) - return false; - - uint32 amt = 0; - auto row = results.begin(); - - amt = atoi(row[0]); - - if(amt == 0) - return false; - - auto iter = zone->VeteranRewards.begin(); - for (; iter != zone->VeteranRewards.end(); ++iter) - if((*iter).claim_id == claim_id) - break; - - if(iter == zone->VeteranRewards.end()) - return false; - - if(amt == 1) { - query = StringFormat("DELETE FROM account_rewards " - "WHERE account_id=%i AND reward_id=%i", - AccountID(), claim_id); - results = database.QueryDatabase(query); - if(!results.ErrorMessage().c_str()) - LogFile->write(EQEMuLog::Error, "Error in Client::TryReward(): %s (%s)", query.c_str(), results.ErrorMessage().c_str()); + return false; + } + char errbuf[MYSQL_ERRMSG_SIZE]; + char* query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + uint32 amt = 0; + + if(database.RunQuery(query,MakeAnyLenString(&query,"SELECT amount FROM" + " account_rewards WHERE account_id=%i AND reward_id=%i", AccountID(), claim_id), + errbuf,&result)) + { + row = mysql_fetch_row(result); + if(row) + { + amt = atoi(row[0]); + } + else + { + mysql_free_result(result); + safe_delete_array(query); + return false; + } + mysql_free_result(result); + safe_delete_array(query); } else { - query = StringFormat("UPDATE account_rewards SET amount=(amount-1) " - "WHERE account_id=%i AND reward_id=%i", AccountID(), claim_id); - results = database.QueryDatabase(query); - if(!results.Success()) - LogFile->write(EQEMuLog::Error, "Error in Client::TryReward(): %s (%s)", query.c_str(), results.ErrorMessage().c_str()); + LogFile->write(EQEMuLog::Error, "Error in Client::TryReward(): %s (%s)", query, errbuf); + safe_delete_array(query); + return false; + } + if(amt == 0) + { + return false; + } + + std::list::iterator iter = zone->VeteranRewards.begin(); + while(iter != zone->VeteranRewards.end()) + { + if((*iter).claim_id == claim_id) + { + break; + } + ++iter; + } + + if(iter == zone->VeteranRewards.end()) + { + return false; + } + + if(amt == 1) + { + if(!database.RunQuery(query,MakeAnyLenString(&query,"DELETE FROM" + " account_rewards WHERE account_id=%i AND reward_id=%i", AccountID(), claim_id), + errbuf)) + { + LogFile->write(EQEMuLog::Error, "Error in Client::TryReward(): %s (%s)", query, errbuf); + safe_delete_array(query); + } + else + { + safe_delete_array(query); + } + } + else + { + if(!database.RunQuery(query,MakeAnyLenString(&query,"UPDATE account_rewards SET amount=(amount-1)" + " WHERE account_id=%i AND reward_id=%i", AccountID(), claim_id), + errbuf)) + { + LogFile->write(EQEMuLog::Error, "Error in Client::TryReward(): %s (%s)", query, errbuf); + safe_delete_array(query); + } + else + { + safe_delete_array(query); + } } InternalVeteranReward ivr = (*iter); @@ -6862,8 +6925,18 @@ void Client::SetAlternateCurrencyValue(uint32 currency_id, uint32 new_amount) SendAlternateCurrencyValue(currency_id); } -void Client::AddAlternateCurrencyValue(uint32 currency_id, int32 amount) +void Client::AddAlternateCurrencyValue(uint32 currency_id, int32 amount, int8 method) { + + /* Added via Quest, rest of the logging methods may be done inline due to information available in that area of the code */ + if (method == 1){ + /* QS: PlayerLogAlternateCurrencyTransactions :: Cursor to Item Storage */ + if (RuleB(QueryServ, PlayerLogAlternateCurrencyTransactions)){ + std::string event_desc = StringFormat("Added via Quest :: Cursor to Item :: alt_currency_id:%i amount:%i in zoneid:%i instid:%i", currency_id, this->GetZoneID(), this->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_Alternate_Currency_Transactions, this->CharacterID(), event_desc); + } + } + if(amount == 0) { return; } @@ -7601,7 +7674,7 @@ void Client::SetFactionLevel(uint32 char_id, uint32 npc_id, uint8 char_class, ui tmpValue = current_value + mod + npc_value[i]; int16 FactionModPct = spellbonuses.FactionModPct + itembonuses.FactionModPct + aabonuses.FactionModPct; - tmpValue += (tmpValue * FactionModPct) / 100; + tmpValue += (tmpValue * FactionModPct) / 100; // Make sure faction hits don't go to GMs... if (m_pp.gm==1 && (tmpValue < current_value)) { @@ -7738,30 +7811,41 @@ void Client::SendFactionMessage(int32 tmpvalue, int32 faction_id, int32 totalval void Client::LoadAccountFlags() { + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + accountflags.clear(); - - std::string query = StringFormat("SELECT p_flag, p_value FROM account_flags WHERE p_accid = '%d'", account_id); - auto results = database.QueryDatabase(query); - if (!results.Success()) { - std::cerr << "Error in LoadAccountFlags query '" << query << "' " << results.ErrorMessage() << std::endl; - return; - } - - for (auto row = results.begin(); row != results.end(); ++row) { - std::string fname(row[0]); - std::string fval(row[1]); - accountflags[fname] = fval; - } - + MakeAnyLenString(&query, "SELECT p_flag, p_value FROM account_flags WHERE p_accid = '%d'", account_id); + if(database.RunQuery(query, strlen(query), errbuf, &result)) + { + while(row = mysql_fetch_row(result)) + { + std::string fname(row[0]); + std::string fval(row[1]); + accountflags[fname] = fval; + } + mysql_free_result(result); + } + else + { + std::cerr << "Error in LoadAccountFlags query '" << query << "' " << errbuf << std::endl; + } + safe_delete_array(query); } void Client::SetAccountFlag(std::string flag, std::string val) { - std::string query = StringFormat("REPLACE INTO account_flags (p_accid, p_flag, p_value) " - "VALUES( '%d', '%s', '%s')", account_id, flag.c_str(), val.c_str()); - auto results = database.QueryDatabase(query); - if(!results.Success()) - std::cerr << "Error in SetAccountFlags query '" << query << "' " << results.ErrorMessage() << std::endl; + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + + MakeAnyLenString(&query, "REPLACE INTO account_flags (p_accid, p_flag, p_value) VALUES( '%d', '%s', '%s')", account_id, flag.c_str(), val.c_str()); + if(!database.RunQuery(query, strlen(query), errbuf)) + { + std::cerr << "Error in SetAccountFlags query '" << query << "' " << errbuf << std::endl; + } + safe_delete_array(query); accountflags[flag] = val; } @@ -7866,7 +7950,7 @@ void Client::TryItemTimer(int slot) } ++it_iter; } - + if(slot > EmuConstants::EQUIPMENT_END) { return; } @@ -8170,21 +8254,29 @@ void Client::PlayMP3(const char* fname) safe_delete(outapp); } -void Client::ExpeditionSay(const char *str, int expID) { +void Client::ExpeditionSay(const char *str, int ExpID) { + char errbuf[MYSQL_ERRMSG_SIZE]; + char* query = 0; + MYSQL_RES *result; + MYSQL_ROW row; - std::string query = StringFormat("SELECT `player_name` FROM " - "`cust_inst_players` WHERE " - "`inst_id` = %i", expID); - auto results = database.QueryDatabase(query); - if (!results.Success()) + if (!database.RunQuery(query,MakeAnyLenString(&query, "SELECT `player_name` FROM `cust_inst_players` WHERE `inst_id` = %i", ExpID),errbuf,&result)){ + safe_delete_array(query); return; - - this->Message(14, "You say to the expedition, '%s'", str); - - for (auto row = results.begin(); row != results.end(); ++row) { - const char* charName = row[0]; - if(strcmp(charName, this->GetCleanName()) != 0) - worldserver.SendEmoteMessage(charName, 0, 0, 14, "%s says to the expedition, '%s'", this->GetCleanName(), str); } + safe_delete_array(query); + + if(result) + this->Message(14, "You say to the expedition, '%s'", str); + + while((row = mysql_fetch_row(result))) { + const char* CharName = row[0]; + if(strcmp(CharName, this->GetCleanName()) != 0) + worldserver.SendEmoteMessage(CharName, 0, 0, 14, "%s says to the expedition, '%s'", this->GetCleanName(), str); + // ChannelList->CreateChannel(ChannelName, ChannelOwner, ChannelPassword, true, atoi(row[3])); + } + + mysql_free_result(result); + } diff --git a/zone/client.cpp.orig b/zone/client.cpp.orig new file mode 100644 index 000000000..cc4d04977 --- /dev/null +++ b/zone/client.cpp.orig @@ -0,0 +1,8232 @@ +/* EQEMu: Everquest Server Emulator + Copyright (C) 2001-2003 EQEMu Development Team (http://eqemulator.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 +*/ +#include "../common/debug.h" +#include +#include +#include +#include +#include +#include +#include +#include + +// for windows compile +#ifdef _WINDOWS + #define abs64 _abs64 +#else + #include + #include + #include + #include "../common/unix.h" + #define abs64 abs +#endif + +extern volatile bool RunLoops; + +#include "../common/features.h" +#include "masterentity.h" +#include "worldserver.h" +#include "../common/misc.h" +#include "zonedb.h" +#include "../common/spdat.h" +#include "net.h" +#include "../common/packet_dump.h" +#include "../common/packet_functions.h" +#include "petitions.h" +#include "../common/serverinfo.h" +#include "../common/zone_numbers.h" +#include "../common/moremath.h" +#include "../common/guilds.h" +#include "../common/breakdowns.h" +#include "../common/rulesys.h" +#include "../common/string_util.h" +#include "forage.h" +#include "command.h" +#include "string_ids.h" +#include "npc_ai.h" +#include "client_logs.h" +#include "guild_mgr.h" +#include "quest_parser_collection.h" +#include "queryserv.h" + +extern QueryServ* QServ; +extern EntityList entity_list; +extern Zone* zone; +extern volatile bool ZoneLoaded; +extern WorldServer worldserver; +extern uint32 numclients; +extern PetitionList petition_list; +bool commandlogged; +char entirecommand[255]; +extern DBAsyncFinishedQueue MTdbafq; +extern DBAsync *dbasync; + +Client::Client(EQStreamInterface* ieqs) +: Mob("No name", // name + "", // lastname + 0, // cur_hp + 0, // max_hp + 0, // gender + 0, // race + 0, // class + BT_Humanoid, // bodytype + 0, // deity + 0, // level + 0, // npctypeid + 0, // size + 0.7, // runspeed + 0, // heading + 0, // x + 0, // y + 0, // z + 0, // light + 0xFF, // texture + 0xFF, // helmtexture + 0, // ac + 0, // atk + 0, // str + 0, // sta + 0, // dex + 0, // agi + 0, // int + 0, // wis + 0, // cha + 0, // Luclin Hair Colour + 0, // Luclin Beard Color + 0, // Luclin Eye1 + 0, // Luclin Eye2 + 0, // Luclin Hair Style + 0, // Luclin Face + 0, // Luclin Beard + 0, // Drakkin Heritage + 0, // Drakkin Tattoo + 0, // Drakkin Details + 0, // Armor Tint + 0xff, // AA Title + 0, // see_invis + 0, // see_invis_undead + 0, + 0, + 0, + 0, + 0, // qglobal + 0, // maxlevel + 0 // scalerate + + ), + //these must be listed in the order they appear in client.h + position_timer(250), + hpupdate_timer(1800), + camp_timer(29000), + process_timer(100), + stamina_timer(40000), + zoneinpacket_timer(3000), + linkdead_timer(RuleI(Zone,ClientLinkdeadMS)), + dead_timer(2000), + global_channel_timer(1000), + shield_timer(500), + fishing_timer(8000), + endupkeep_timer(1000), + forget_timer(0), + autosave_timer(RuleI(Character, AutosaveIntervalS)*1000), +#ifdef REVERSE_AGGRO + scanarea_timer(AIClientScanarea_delay), +#endif + tribute_timer(Tribute_duration), + proximity_timer(ClientProximity_interval), + TaskPeriodic_Timer(RuleI(TaskSystem, PeriodicCheckTimer) * 1000), + charm_update_timer(6000), + rest_timer(1), + charm_class_attacks_timer(3000), + charm_cast_timer(3500), + qglobal_purge_timer(30000), + TrackingTimer(2000), + RespawnFromHoverTimer(0), + merc_timer(RuleI(Mercs, UpkeepIntervalMS)), + ItemTickTimer(10000), + ItemQuestTimer(500) +{ + for(int cf=0; cf < _FilterCount; cf++) + ClientFilters[cf] = FilterShow; + character_id = 0; + conn_state = NoPacketsReceived; + client_data_loaded = false; + feigned = false; + berserk = false; + dead = false; + eqs = ieqs; + ip = eqs->GetRemoteIP(); + port = ntohs(eqs->GetRemotePort()); + client_state = CLIENT_CONNECTING; + Trader=false; + Buyer = false; + CustomerID = 0; + TrackingID = 0; + WID = 0; + account_id = 0; + admin = 0; + lsaccountid = 0; + shield_target = nullptr; + SQL_log = nullptr; + guild_id = GUILD_NONE; + guildrank = 0; + GuildBanker = false; + memset(lskey, 0, sizeof(lskey)); + strcpy(account_name, ""); + tellsoff = false; + last_reported_mana = 0; + last_reported_endur = 0; + gmhideme = false; + AFK = false; + LFG = false; + LFGFromLevel = 0; + LFGToLevel = 0; + LFGMatchFilter = false; + LFGComments[0] = '\0'; + LFP = false; + gmspeed = 0; + playeraction = 0; + SetTarget(0); + auto_attack = false; + auto_fire = false; + linkdead_timer.Disable(); + zonesummon_x = -2; + zonesummon_y = -2; + zonesummon_z = -2; + zonesummon_id = 0; + zonesummon_ignorerestrictions = 0; + zoning = false; + zone_mode = ZoneUnsolicited; + proximity_x = FLT_MAX; //arbitrary large number + proximity_y = FLT_MAX; + proximity_z = FLT_MAX; + casting_spell_id = 0; + npcflag = false; + npclevel = 0; + pQueuedSaveWorkID = 0; + position_timer_counter = 0; + fishing_timer.Disable(); + shield_timer.Disable(); + dead_timer.Disable(); + camp_timer.Disable(); + autosave_timer.Disable(); + GetMercTimer()->Disable(); + instalog = false; + pLastUpdate = 0; + pLastUpdateWZ = 0; + m_pp.autosplit = false; + // initialise haste variable + m_tradeskill_object = nullptr; + delaytimer = false; + PendingRezzXP = -1; + PendingRezzDBID = 0; + PendingRezzSpellID = 0; + numclients++; + // emuerror; + UpdateWindowTitle(); + horseId = 0; + tgb = false; + tribute_master_id = 0xFFFFFFFF; + tribute_timer.Disable(); + taskstate = nullptr; + TotalSecondsPlayed = 0; + keyring.clear(); + bind_sight_target = nullptr; + mercid = 0; + mercSlot = 0; + InitializeMercInfo(); + SetMerc(0); + + logging_enabled = CLIENT_DEFAULT_LOGGING_ENABLED; + + //for good measure: + memset(&m_pp, 0, sizeof(m_pp)); + memset(&m_epp, 0, sizeof(m_epp)); + PendingTranslocate = false; + PendingSacrifice = false; + BoatID = 0; + + KarmaUpdateTimer = new Timer(RuleI(Chat, KarmaUpdateIntervalMS)); + GlobalChatLimiterTimer = new Timer(RuleI(Chat, IntervalDurationMS)); + AttemptedMessages = 0; + TotalKarma = 0; + ClientVersion = EQClientUnknown; + ClientVersionBit = 0; + AggroCount = 0; + RestRegenHP = 0; + RestRegenMana = 0; + RestRegenEndurance = 0; + XPRate = 100; + cur_end = 0; + + m_TimeSinceLastPositionCheck = 0; + m_DistanceSinceLastPositionCheck = 0.0f; + m_ShadowStepExemption = 0; + m_KnockBackExemption = 0; + m_PortExemption = 0; + m_SenseExemption = 0; + m_AssistExemption = 0; + m_CheatDetectMoved = false; + CanUseReport = true; + aa_los_me.x = 0; + aa_los_me.y = 0; + aa_los_me.z = 0; + aa_los_me_heading = 0; + aa_los_them.x = 0; + aa_los_them.y = 0; + aa_los_them.z = 0; + aa_los_them_mob = nullptr; + los_status = false; + los_status_facing = false; + qGlobals = nullptr; + HideCorpseMode = HideCorpseNone; + PendingGuildInvitation = false; + + cur_end = 0; + + InitializeBuffSlots(); + + adventure_request_timer = nullptr; + adventure_create_timer = nullptr; + adventure_leave_timer = nullptr; + adventure_door_timer = nullptr; + adv_requested_data = nullptr; + adventure_stats_timer = nullptr; + adventure_leaderboard_timer = nullptr; + adv_data = nullptr; + adv_requested_theme = 0; + adv_requested_id = 0; + adv_requested_member_count = 0; + + for(int i = 0; i < XTARGET_HARDCAP; ++i) + { + XTargets[i].Type = Auto; + XTargets[i].ID = 0; + XTargets[i].Name[0] = 0; + } + MaxXTargets = 5; + XTargetAutoAddHaters = true; + LoadAccountFlags(); + + initial_respawn_selection = 0; + alternate_currency_loaded = false; + + EngagedRaidTarget = false; + SavedRaidRestTimer = 0; +} + +Client::~Client() { +#ifdef BOTS + Bot::ProcessBotOwnerRefDelete(this); +#endif + if(IsInAGuild()) + guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), 0, time(nullptr)); + + Mob* horse = entity_list.GetMob(this->CastToClient()->GetHorseId()); + if (horse) + horse->Depop(); + + Mob* merc = entity_list.GetMob(this->GetMercID()); + if (merc) + merc->Depop(); + + if(Trader) + database.DeleteTraderItem(this->CharacterID()); + + if(Buyer) + ToggleBuyerMode(false); + + if(conn_state != ClientConnectFinished) { + LogFile->write(EQEMuLog::Debug, "Client '%s' was destroyed before reaching the connected state:", GetName()); + ReportConnectingState(); + } + + if(m_tradeskill_object != nullptr) { + m_tradeskill_object->Close(); + m_tradeskill_object = nullptr; + } + +#ifdef CLIENT_LOGS + client_logs.unsubscribeAll(this); +#endif + + ChangeSQLLog(nullptr); + if(IsDueling() && GetDuelTarget() != 0) { + Entity* entity = entity_list.GetID(GetDuelTarget()); + if(entity != nullptr && entity->IsClient()) { + entity->CastToClient()->SetDueling(false); + entity->CastToClient()->SetDuelTarget(0); + entity_list.DuelMessage(entity->CastToClient(),this,true); + } + } + + if (shield_target) { + for (int y = 0; y < 2; y++) { + if (shield_target->shielder[y].shielder_id == GetID()) { + shield_target->shielder[y].shielder_id = 0; + shield_target->shielder[y].shielder_bonus = 0; + } + } + shield_target = nullptr; + } + + if(GetTarget()) + GetTarget()->IsTargeted(-1); + + //if we are in a group and we are not zoning, force leave the group + if(isgrouped && !zoning && ZoneLoaded) + LeaveGroup(); + + UpdateWho(2); + + if(IsHoveringForRespawn()) + { + m_pp.zone_id = m_pp.binds[0].zoneId; + m_pp.zoneInstance = 0; + x_pos = m_pp.binds[0].x; + y_pos = m_pp.binds[0].y; + z_pos = m_pp.binds[0].z; + } + + // we save right now, because the client might be zoning and the world + // will need this data right away + Save(2); // This fails when database destructor is called first on shutdown + + safe_delete(taskstate); + safe_delete(KarmaUpdateTimer); + safe_delete(GlobalChatLimiterTimer); + safe_delete(qGlobals); + safe_delete(adventure_request_timer); + safe_delete(adventure_create_timer); + safe_delete(adventure_leave_timer); + safe_delete(adventure_door_timer); + safe_delete(adventure_stats_timer); + safe_delete(adventure_leaderboard_timer); + safe_delete_array(adv_requested_data); + safe_delete_array(adv_data); + + ClearRespawnOptions(); + + numclients--; + UpdateWindowTitle(); + if(zone) + zone->RemoveAuth(GetName()); + + //let the stream factory know were done with this stream + eqs->Close(); + eqs->ReleaseFromUse(); + + UninitializeBuffSlots(); +} + +void Client::SendLogoutPackets() { + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_CancelTrade, sizeof(CancelTrade_Struct)); + CancelTrade_Struct* ct = (CancelTrade_Struct*) outapp->pBuffer; + ct->fromid = GetID(); + ct->action = groupActUpdate; + FastQueuePacket(&outapp); + + outapp = new EQApplicationPacket(OP_PreLogoutReply); + FastQueuePacket(&outapp); + +} + +void Client::ReportConnectingState() { + switch(conn_state) { + case NoPacketsReceived: //havent gotten anything + LogFile->write(EQEMuLog::Debug, "Client has not sent us an initial zone entry packet."); + break; + case ReceivedZoneEntry: //got the first packet, loading up PP + LogFile->write(EQEMuLog::Debug, "Client sent initial zone packet, but we never got their player info from the database."); + break; + case PlayerProfileLoaded: //our DB work is done, sending it + LogFile->write(EQEMuLog::Debug, "We were sending the player profile, tributes, tasks, spawns, time and weather, but never finished."); + break; + case ZoneInfoSent: //includes PP, tributes, tasks, spawns, time and weather + LogFile->write(EQEMuLog::Debug, "We successfully sent player info and spawns, waiting for client to request new zone."); + break; + case NewZoneRequested: //received and sent new zone request + LogFile->write(EQEMuLog::Debug, "We received client's new zone request, waiting for client spawn request."); + break; + case ClientSpawnRequested: //client sent ReqClientSpawn + LogFile->write(EQEMuLog::Debug, "We received the client spawn request, and were sending objects, doors, zone points and some other stuff, but never finished."); + break; + case ZoneContentsSent: //objects, doors, zone points + LogFile->write(EQEMuLog::Debug, "The rest of the zone contents were successfully sent, waiting for client ready notification."); + break; + case ClientReadyReceived: //client told us its ready, send them a bunch of crap like guild MOTD, etc + LogFile->write(EQEMuLog::Debug, "We received client ready notification, but never finished Client::CompleteConnect"); + break; + case ClientConnectFinished: //client finally moved to finished state, were done here + LogFile->write(EQEMuLog::Debug, "Client is successfully connected."); + break; + }; +} + +bool Client::Save(uint8 iCommitNow) { +#if 0 +// Orig. Offset: 344 / 0x00000000 +// Length: 36 / 0x00000024 + unsigned char rawData[36] = +{ + 0x0D, 0x30, 0xE1, 0x30, 0x1E, 0x10, 0x22, 0x10, 0x20, 0x10, 0x21, 0x10, 0x1C, 0x20, 0x1F, 0x10, + 0x7C, 0x10, 0x68, 0x10, 0x51, 0x10, 0x78, 0x10, 0xBD, 0x10, 0xD2, 0x10, 0xCD, 0x10, 0xD1, 0x10, + 0x01, 0x10, 0x6D, 0x10 +} ; + for (int tmp = 0;tmp <=35;tmp++){ + m_pp.unknown0256[89+tmp] = rawData[tmp]; + } +#endif + + if(!ClientDataLoaded()) + return false; + + m_pp.x = x_pos; + m_pp.y = y_pos; + m_pp.z = z_pos; + m_pp.guildrank=guildrank; + m_pp.heading = heading; + + // Temp Hack for signed values until we get the root of the problem changed over to signed... + if (m_pp.copper < 0) { m_pp.copper = 0; } + if (m_pp.silver < 0) { m_pp.silver = 0; } + if (m_pp.gold < 0) { m_pp.gold = 0; } + if (m_pp.platinum < 0) { m_pp.platinum = 0; } + if (m_pp.copper_bank < 0) { m_pp.copper_bank = 0; } + if (m_pp.silver_bank < 0) { m_pp.silver_bank = 0; } + if (m_pp.gold_bank < 0) { m_pp.gold_bank = 0; } + if (m_pp.platinum_bank < 0) { m_pp.platinum_bank = 0; } + + + int spentpoints=0; + for(int a=0;a < MAX_PP_AA_ARRAY;a++) { + uint32 points = aa[a]->value; + if(points > HIGHEST_AA_VALUE) // Unifying this + { + aa[a]->value = HIGHEST_AA_VALUE; + points = HIGHEST_AA_VALUE; + } + if (points > 0) + { + SendAA_Struct* curAA = zone->FindAA(aa[a]->AA-aa[a]->value+1); + if(curAA) + { + for (int rank=0; rank::iterator RequiredLevel = AARequiredLevelAndCost.find(aa[a]->AA-aa[a]->value + 1 + rank); + + if(RequiredLevel != AARequiredLevelAndCost.end()) + { + spentpoints += RequiredLevel->second.Cost; + } + else + spentpoints += (curAA->cost + (curAA->cost_inc * rank)); + } + } + } + } + + m_pp.aapoints_spent = spentpoints + m_epp.expended_aa; + + if (GetHP() <= 0) { + m_pp.cur_hp = GetMaxHP(); + } + else + m_pp.cur_hp = GetHP(); + + m_pp.mana = cur_mana; + m_pp.endurance = cur_end; + + database.SaveBuffs(this); + + TotalSecondsPlayed += (time(nullptr) - m_pp.lastlogin); + m_pp.timePlayedMin = (TotalSecondsPlayed / 60); + m_pp.RestTimer = rest_timer.GetRemainingTime() / 1000; + + if(GetMercInfo().MercTimerRemaining > RuleI(Mercs, UpkeepIntervalMS)) + GetMercInfo().MercTimerRemaining = RuleI(Mercs, UpkeepIntervalMS); + + if(GetMercTimer()->Enabled()) { + GetMercInfo().MercTimerRemaining = GetMercTimer()->GetRemainingTime(); + } + + if (GetMerc() && !dead) { + + } else { + memset(&m_mercinfo, 0, sizeof(struct MercInfo)); + } + + m_pp.lastlogin = time(nullptr); + if (pQueuedSaveWorkID) { + dbasync->CancelWork(pQueuedSaveWorkID); + pQueuedSaveWorkID = 0; + } + + if (GetPet() && !GetPet()->IsFamiliar() && GetPet()->CastToNPC()->GetPetSpellID() && !dead) { + NPC *pet = GetPet()->CastToNPC(); + m_petinfo.SpellID = pet->CastToNPC()->GetPetSpellID(); + m_petinfo.HP = pet->GetHP(); + m_petinfo.Mana = pet->GetMana(); + pet->GetPetState(m_petinfo.Buffs, m_petinfo.Items, m_petinfo.Name); + m_petinfo.petpower = pet->GetPetPower(); + m_petinfo.size = pet->GetSize(); + } else { + memset(&m_petinfo, 0, sizeof(struct PetInfo)); + } + database.SavePetInfo(this); + + if(tribute_timer.Enabled()) { + m_pp.tribute_time_remaining = tribute_timer.GetRemainingTime(); + } else { + m_pp.tribute_time_remaining = 0xFFFFFFFF; + m_pp.tribute_active = 0; + } + + p_timers.Store(&database); + +// printf("Dumping inventory on save:\n"); +// m_inv.dumpEntireInventory(); + + SaveTaskState(); + if (iCommitNow <= 1) { + char* query = 0; + uint32_breakdown workpt; + workpt.b4() = DBA_b4_Entity; + workpt.w2_3() = GetID(); + workpt.b1() = DBA_b1_Entity_Client_Save; + DBAsyncWork* dbaw = new DBAsyncWork(&database, &MTdbafq, workpt, DBAsync::Write, 0xFFFFFFFF); + dbaw->AddQuery(iCommitNow == 0 ? true : false, &query, database.SetPlayerProfile_MQ(&query, account_id, character_id, &m_pp, &m_inv, &m_epp, 0, 0, MaxXTargets), false); + if (iCommitNow == 0){ + pQueuedSaveWorkID = dbasync->AddWork(&dbaw, 2500); + } + else { + dbasync->AddWork(&dbaw, 0); + SaveBackup(); + } + safe_delete_array(query); + return true; + } + else if (database.SetPlayerProfile(account_id, character_id, &m_pp, &m_inv, &m_epp, 0, 0, MaxXTargets)) { + SaveBackup(); + } + else { + std::cerr << "Failed to update player profile" << std::endl; + return false; + } + + /* Mirror Character Data */ + database.StoreCharacterLookup(this->CharacterID()); + + return true; +} + +void Client::SaveBackup() { + if (!RunLoops) + return; + char* query = 0; + DBAsyncWork* dbaw = new DBAsyncWork(&database, &DBAsyncCB_CharacterBackup, this->CharacterID(), DBAsync::Read); + dbaw->AddQuery(0, &query, MakeAnyLenString(&query, "Select id, UNIX_TIMESTAMP()-UNIX_TIMESTAMP(ts) as age from character_backup where charid=%u and backupreason=0 order by ts asc", this->CharacterID()), true); + dbasync->AddWork(&dbaw, 0); +} + +CLIENTPACKET::CLIENTPACKET() +{ + app = nullptr; + ack_req = false; +} + +CLIENTPACKET::~CLIENTPACKET() +{ + safe_delete(app); +} + +//this assumes we do not own pApp, and clones it. +bool Client::AddPacket(const EQApplicationPacket *pApp, bool bAckreq) { + if (!pApp) + return false; + if(!zoneinpacket_timer.Enabled()) { + //drop the packet because it will never get sent. + return(false); + } + CLIENTPACKET *c = new CLIENTPACKET; + + c->ack_req = bAckreq; + c->app = pApp->Copy(); + + clientpackets.Append(c); + return true; +} + +//this assumes that it owns the object pointed to by *pApp +bool Client::AddPacket(EQApplicationPacket** pApp, bool bAckreq) { + if (!pApp || !(*pApp)) + return false; + if(!zoneinpacket_timer.Enabled()) { + //drop the packet because it will never get sent. + return(false); + } + CLIENTPACKET *c = new CLIENTPACKET; + + c->ack_req = bAckreq; + c->app = *pApp; + *pApp = 0; + + clientpackets.Append(c); + return true; +} + +bool Client::SendAllPackets() { + LinkedListIterator iterator(clientpackets); + + CLIENTPACKET* cp = 0; + iterator.Reset(); + while(iterator.MoreElements()) { + cp = iterator.GetData(); + if(eqs) + eqs->FastQueuePacket((EQApplicationPacket **)&cp->app, cp->ack_req); + iterator.RemoveCurrent(); +#if EQDEBUG >= 6 + LogFile->write(EQEMuLog::Normal, "Transmitting a packet"); +#endif + } + return true; +} + +void Client::QueuePacket(const EQApplicationPacket* app, bool ack_req, CLIENT_CONN_STATUS required_state, eqFilterType filter) { + if(filter!=FilterNone){ + //this is incomplete... no support for FilterShowGroupOnly or FilterShowSelfOnly + if(GetFilter(filter) == FilterHide) + return; //Client has this filter on, no need to send packet + } + if(client_state != CLIENT_CONNECTED && required_state == CLIENT_CONNECTED){ + AddPacket(app, ack_req); + return; + } + + // if the program doesnt care about the status or if the status isnt what we requested + if (required_state != CLIENT_CONNECTINGALL && client_state != required_state) + { + // todo: save packets for later use + AddPacket(app, ack_req); + } + else + if(eqs) + eqs->QueuePacket(app, ack_req); +} + +void Client::FastQueuePacket(EQApplicationPacket** app, bool ack_req, CLIENT_CONN_STATUS required_state) { + + //std::cout << "Sending: 0x" << std::hex << std::setw(4) << std::setfill('0') << (*app)->GetOpcode() << std::dec << ", size=" << (*app)->size << std::endl; + + // if the program doesnt care about the status or if the status isnt what we requested + if (required_state != CLIENT_CONNECTINGALL && client_state != required_state) { + // todo: save packets for later use + AddPacket(app, ack_req); +// LogFile->write(EQEMuLog::Normal, "Adding Packet to list (%d) (%d)", (*app)->GetOpcode(), (int)required_state); + return; + } + else { + if(eqs) + eqs->FastQueuePacket((EQApplicationPacket **)app, ack_req); + else if (app && (*app)) + delete *app; + *app = 0; + } + return; +} + +void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_skill, const char* orig_message, const char* targetname) { + char message[4096]; + strn0cpy(message, orig_message, sizeof(message)); + + + #if EQDEBUG >= 11 + LogFile->write(EQEMuLog::Debug,"Client::ChannelMessageReceived() Channel:%i message:'%s'", chan_num, message); + #endif + + if (targetname == nullptr) { + targetname = (!GetTarget()) ? "" : GetTarget()->GetName(); + } + + if(RuleB(Chat, EnableAntiSpam)) + { + if(strcmp(targetname, "discard") != 0) + { + if(chan_num == 3 || chan_num == 4 || chan_num == 5 || chan_num == 7) + { + if(GlobalChatLimiterTimer) + { + if(GlobalChatLimiterTimer->Check(false)) + { + GlobalChatLimiterTimer->Start(RuleI(Chat, IntervalDurationMS)); + AttemptedMessages = 0; + } + } + + uint32 AllowedMessages = RuleI(Chat, MinimumMessagesPerInterval) + TotalKarma; + AllowedMessages = AllowedMessages > RuleI(Chat, MaximumMessagesPerInterval) ? RuleI(Chat, MaximumMessagesPerInterval) : AllowedMessages; + + if(RuleI(Chat, MinStatusToBypassAntiSpam) <= Admin()) + AllowedMessages = 10000; + + AttemptedMessages++; + if(AttemptedMessages > AllowedMessages) + { + if(AttemptedMessages > RuleI(Chat, MaxMessagesBeforeKick)) + { + Kick(); + return; + } + if(GlobalChatLimiterTimer) + { + Message(0, "You have been rate limited, you can send more messages in %i seconds.", + GlobalChatLimiterTimer->GetRemainingTime() / 1000); + return; + } + else + { + Message(0, "You have been rate limited, you can send more messages in 60 seconds."); + return; + } + } + } + } + } + + /* Logs Player Chat */ + if (RuleB(QueryServ, PlayerLogChat)) { + ServerPacket* pack = new ServerPacket(ServerOP_Speech, sizeof(Server_Speech_Struct) + strlen(message) + 1); + Server_Speech_Struct* sem = (Server_Speech_Struct*) pack->pBuffer; + + if(chan_num == 0) + sem->guilddbid = GuildID(); + else + sem->guilddbid = 0; + + strcpy(sem->message, message); + sem->minstatus = this->Admin(); + sem->type = chan_num; + if(targetname != 0) + strcpy(sem->to, targetname); + + if(GetName() != 0) + strcpy(sem->from, GetName()); + + pack->Deflate(); + if(worldserver.Connected()) + worldserver.SendPacket(pack); + safe_delete(pack); + } + + //Return true to proceed, false to return + if(!mod_client_message(message, chan_num)) { return; } + + // Garble the message based on drunkness + if (m_pp.intoxication > 0) { + GarbleMessage(message, (int)(m_pp.intoxication / 3)); + language = 0; // No need for language when drunk + } + + switch(chan_num) + { + case 0: { /* Guild Chat */ + if (!IsInAGuild()) + Message_StringID(MT_DefaultText, GUILD_NOT_MEMBER2); //You are not a member of any guild. + else if (!guild_mgr.CheckPermission(GuildID(), GuildRank(), GUILD_SPEAK)) + Message(0, "Error: You dont have permission to speak to the guild."); + else if (!worldserver.SendChannelMessage(this, targetname, chan_num, GuildID(), language, message)) + Message(0, "Error: World server disconnected"); + break; + } + case 2: { /* Group Chat */ + Raid* raid = entity_list.GetRaidByClient(this); + if(raid) { + raid->RaidGroupSay((const char*) message, this); + break; + } + + Group* group = GetGroup(); + if(group != nullptr) { + group->GroupMessage(this,language,lang_skill,(const char*) message); + } + break; + } + case 15: { /* Raid Say */ + Raid* raid = entity_list.GetRaidByClient(this); + if(raid){ + raid->RaidSay((const char*) message, this); + } + break; + } + case 3: { /* Shout */ + Mob *sender = this; + if (GetPet() && GetPet()->FindType(SE_VoiceGraft)) + sender = GetPet(); + + entity_list.ChannelMessage(sender, chan_num, language, lang_skill, message); + break; + } + case 4: { /* Auction */ + if(RuleB(Chat, ServerWideAuction)) + { + if(!global_channel_timer.Check()) + { + if(strlen(targetname) == 0) + ChannelMessageReceived(5, language, lang_skill, message, "discard"); //Fast typer or spammer?? + else + return; + } + + if(GetRevoked()) + { + Message(0, "You have been revoked. You may not talk on Auction."); + return; + } + + if(TotalKarma < RuleI(Chat, KarmaGlobalChatLimit)) + { + if(GetLevel() < RuleI(Chat, GlobalChatLevelLimit)) + { + Message(0, "You do not have permission to talk in Auction at this time."); + return; + } + } + + if (!worldserver.SendChannelMessage(this, 0, 4, 0, language, message)) + Message(0, "Error: World server disconnected"); + } + else if(!RuleB(Chat, ServerWideAuction)) { + Mob *sender = this; + + if (GetPet() && GetPet()->FindType(SE_VoiceGraft)) + sender = GetPet(); + + entity_list.ChannelMessage(sender, chan_num, language, message); + } + break; + } + case 5: { /* OOC */ + if(RuleB(Chat, ServerWideOOC)) + { + if(!global_channel_timer.Check()) + { + if(strlen(targetname) == 0) + ChannelMessageReceived(5, language, lang_skill, message, "discard"); //Fast typer or spammer?? + else + return; + } + if(worldserver.IsOOCMuted() && admin < 100) + { + Message(0,"OOC has been muted. Try again later."); + return; + } + + if(GetRevoked()) + { + Message(0, "You have been revoked. You may not talk on OOC."); + return; + } + + if(TotalKarma < RuleI(Chat, KarmaGlobalChatLimit)) + { + if(GetLevel() < RuleI(Chat, GlobalChatLevelLimit)) + { + Message(0, "You do not have permission to talk in OOC at this time."); + return; + } + } + + if (!worldserver.SendChannelMessage(this, 0, 5, 0, language, message)) + { + Message(0, "Error: World server disconnected"); + } + } + else if(!RuleB(Chat, ServerWideOOC)) + { + Mob *sender = this; + + if (GetPet() && GetPet()->FindType(SE_VoiceGraft)) + sender = GetPet(); + + entity_list.ChannelMessage(sender, chan_num, language, message); + } + break; + } + case 6: /* Broadcast */ + case 11: { /* GM Say */ + if (!(admin >= 80)) + Message(0, "Error: Only GMs can use this channel"); + else if (!worldserver.SendChannelMessage(this, targetname, chan_num, 0, language, message)) + Message(0, "Error: World server disconnected"); + break; + } + case 7: { /* Tell */ + if(!global_channel_timer.Check()) + { + if(strlen(targetname) == 0) + ChannelMessageReceived(7, language, lang_skill, message, "discard"); //Fast typer or spammer?? + else + return; + } + + if(GetRevoked()) + { + Message(0, "You have been revoked. You may not send tells."); + return; + } + + if(TotalKarma < RuleI(Chat, KarmaGlobalChatLimit)) + { + if(GetLevel() < RuleI(Chat, GlobalChatLevelLimit)) + { + Message(0, "You do not have permission to send tells at this time."); + return; + } + } + + char target_name[64]; + + if(targetname) + { + size_t i = strlen(targetname); + int x; + for(x = 0; x < i; ++x) + { + if(targetname[x] == '%') + { + target_name[x] = '/'; + } + else + { + target_name[x] = targetname[x]; + } + } + target_name[x] = '\0'; + } + + if(!worldserver.SendChannelMessage(this, target_name, chan_num, 0, language, message)) + Message(0, "Error: World server disconnected"); + break; + } + case 8: { /* Say */ + if(message[0] == COMMAND_CHAR) { + if(command_dispatch(this, message) == -2) { + if(parse->PlayerHasQuestSub(EVENT_COMMAND)) { + int i = parse->EventPlayer(EVENT_COMMAND, this, message, 0); + if(i == 0 && !RuleB(Chat, SuppressCommandErrors)) { + Message(13, "Command '%s' not recognized.", message); + } + } else { + if(!RuleB(Chat, SuppressCommandErrors)) + Message(13, "Command '%s' not recognized.", message); + } + } + break; + } + + Mob* sender = this; + if (GetPet() && GetPet()->FindType(SE_VoiceGraft)) + sender = GetPet(); + + entity_list.ChannelMessage(sender, chan_num, language, lang_skill, message); + parse->EventPlayer(EVENT_SAY, this, message, language); + + if (sender != this) + break; + + if(quest_manager.ProximitySayInUse()) + entity_list.ProcessProximitySay(message, this, language); + + if (GetTarget() != 0 && GetTarget()->IsNPC()) { + if(!GetTarget()->CastToNPC()->IsEngaged()) { + CheckLDoNHail(GetTarget()); + CheckEmoteHail(GetTarget(), message); + + + if(DistNoRootNoZ(*GetTarget()) <= 200) { + NPC *tar = GetTarget()->CastToNPC(); + parse->EventNPC(EVENT_SAY, tar->CastToNPC(), this, message, language); + + if(RuleB(TaskSystem, EnableTaskSystem)) { + if(UpdateTasksOnSpeakWith(tar->GetNPCTypeID())) { + tar->DoQuestPause(this); + } + } + } + } + else { + if (DistNoRootNoZ(*GetTarget()) <= 200) { + parse->EventNPC(EVENT_AGGRO_SAY, GetTarget()->CastToNPC(), this, message, language); + } + } + + } + break; + } + case 20: + { + // UCS Relay for Underfoot and later. + if(!worldserver.SendChannelMessage(this, 0, chan_num, 0, language, message)) + Message(0, "Error: World server disconnected"); + break; + } + case 22: + { + // Emotes for Underfoot and later. + // crash protection -- cheater + message[1023] = '\0'; + size_t msg_len = strlen(message); + if (msg_len > 512) + message[512] = '\0'; + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_Emote, 4 + msg_len + strlen(GetName()) + 2); + Emote_Struct* es = (Emote_Struct*)outapp->pBuffer; + char *Buffer = (char *)es; + Buffer += 4; + snprintf(Buffer, sizeof(Emote_Struct) - 4, "%s %s", GetName(), message); + entity_list.QueueCloseClients(this, outapp, true, 100, 0, true, FilterSocials); + safe_delete(outapp); + break; + } + default: { + Message(0, "Channel (%i) not implemented", (uint16)chan_num); + } + } +} + +// if no language skill is specified, call the function with a skill of 100. +void Client::ChannelMessageSend(const char* from, const char* to, uint8 chan_num, uint8 language, const char* message, ...) { + ChannelMessageSend(from, to, chan_num, language, 100, message); +} + +void Client::ChannelMessageSend(const char* from, const char* to, uint8 chan_num, uint8 language, uint8 lang_skill, const char* message, ...) { + if ((chan_num==11 && !(this->GetGM())) || (chan_num==10 && this->Admin()<80)) // dont need to send /pr & /petition to everybody + return; + va_list argptr; + char buffer[4096]; + char message_sender[64]; + + va_start(argptr, message); + vsnprintf(buffer, 4096, message, argptr); + va_end(argptr); + + EQApplicationPacket app(OP_ChannelMessage, sizeof(ChannelMessage_Struct)+strlen(buffer)+1); + ChannelMessage_Struct* cm = (ChannelMessage_Struct*)app.pBuffer; + + if (from == 0) + strcpy(cm->sender, "ZServer"); + else if (from[0] == 0) + strcpy(cm->sender, "ZServer"); + else { + CleanMobName(from, message_sender); + strcpy(cm->sender, message_sender); + } + if (to != 0) + strcpy((char *) cm->targetname, to); + else if (chan_num == 7) + strcpy(cm->targetname, m_pp.name); + else + cm->targetname[0] = 0; + + uint8 ListenerSkill; + + if (language < MAX_PP_LANGUAGE) { + ListenerSkill = m_pp.languages[language]; + if (ListenerSkill == 0) { + cm->language = (MAX_PP_LANGUAGE - 1); // in an unknown tongue + } + else { + cm->language = language; + } + } + else { + ListenerSkill = m_pp.languages[0]; + cm->language = 0; + } + + // set effective language skill = lower of sender and receiver skills + int32 EffSkill = (lang_skill < ListenerSkill ? lang_skill : ListenerSkill); + if (EffSkill > 100) // maximum language skill is 100 + EffSkill = 100; + cm->skill_in_language = EffSkill; + + // Garble the message based on listener skill + if (ListenerSkill < 100) { + GarbleMessage(buffer, (100 - ListenerSkill)); + } + + cm->chan_num = chan_num; + strcpy(&cm->message[0], buffer); + QueuePacket(&app); + + if ((chan_num == 2) && (ListenerSkill < 100)) { // group message in unmastered language, check for skill up + if ((m_pp.languages[language] <= lang_skill) && (from != this->GetName())) + CheckLanguageSkillIncrease(language, lang_skill); + } +} + +void Client::Message(uint32 type, const char* message, ...) { + if (GetFilter(FilterSpellDamage) == FilterHide && type == MT_NonMelee) + return; + if (GetFilter(FilterMeleeCrits) == FilterHide && type == MT_CritMelee) //98 is self... + return; + if (GetFilter(FilterSpellCrits) == FilterHide && type == MT_SpellCrits) + return; + + va_list argptr; + char *buffer = new char[4096]; + va_start(argptr, message); + vsnprintf(buffer, 4096, message, argptr); + va_end(argptr); + + size_t len = strlen(buffer); + + //client dosent like our packet all the time unless + //we make it really big, then it seems to not care that + //our header is malformed. + //len = 4096 - sizeof(SpecialMesg_Struct); + + uint32 len_packet = sizeof(SpecialMesg_Struct)+len; + EQApplicationPacket* app = new EQApplicationPacket(OP_SpecialMesg, len_packet); + SpecialMesg_Struct* sm=(SpecialMesg_Struct*)app->pBuffer; + sm->header[0] = 0x00; // Header used for #emote style messages.. + sm->header[1] = 0x00; // Play around with these to see other types + sm->header[2] = 0x00; + sm->msg_type = type; + memcpy(sm->message, buffer, len+1); + + FastQueuePacket(&app); + + safe_delete_array(buffer); +} + +void Client::QuestJournalledMessage(const char *npcname, const char* message) { + + // npcnames longer than 60 characters crash the client when they log back in + const int MaxNPCNameLength = 60; + // I assume there is an upper safe limit on the message length. Don't know what it is, but 4000 doesn't crash + // the client. + const int MaxMessageLength = 4000; + + char OutNPCName[MaxNPCNameLength+1]; + char OutMessage[MaxMessageLength+1]; + + // Apparently Visual C++ snprintf is not C99 compliant and doesn't put the null terminator + // in if the formatted string >= the maximum length, so we put it in. + // + snprintf(OutNPCName, MaxNPCNameLength, "%s", npcname); OutNPCName[MaxNPCNameLength]='\0'; + snprintf(OutMessage, MaxMessageLength, "%s", message); OutMessage[MaxMessageLength]='\0'; + + uint32 len_packet = sizeof(SpecialMesg_Struct) + strlen(OutNPCName) + strlen(OutMessage); + EQApplicationPacket* app = new EQApplicationPacket(OP_SpecialMesg, len_packet); + SpecialMesg_Struct* sm=(SpecialMesg_Struct*)app->pBuffer; + + sm->header[0] = 0; + sm->header[1] = 2; + sm->header[2] = 0; + sm->msg_type = 0x0a; + sm->target_spawn_id = GetID(); + + char *dest = &sm->sayer[0]; + + memcpy(dest, OutNPCName, strlen(OutNPCName) + 1); + + dest = dest + strlen(OutNPCName) + 13; + + memcpy(dest, OutMessage, strlen(OutMessage) + 1); + + QueuePacket(app); + + safe_delete(app); +} + +void Client::SetMaxHP() { + if(dead) + return; + SetHP(CalcMaxHP()); + SendHPUpdate(); + Save(); +} + +bool Client::UpdateLDoNPoints(int32 points, uint32 theme) +{ + +/* make sure total stays in sync with individual buckets + m_pp.ldon_points_available = m_pp.ldon_points_guk + +m_pp.ldon_points_mir + +m_pp.ldon_points_mmc + +m_pp.ldon_points_ruj + +m_pp.ldon_points_tak; */ + + if(points < 0) + { + if(m_pp.ldon_points_available < (0-points)) + return false; + } + switch(theme) + { + // handle generic points (theme=0) + case 0: + { // no theme, so distribute evenly across all + int splitpts=points/5; + int gukpts=splitpts+(points%5); + int mirpts=splitpts; + int mmcpts=splitpts; + int rujpts=splitpts; + int takpts=splitpts; + + splitpts=0; + + if(points < 0) + { + if(m_pp.ldon_points_available < (0-points)) + { + return false; + } + if(m_pp.ldon_points_guk < (0-gukpts)) + { + mirpts+=gukpts+m_pp.ldon_points_guk; + gukpts=0-m_pp.ldon_points_guk; + } + if(m_pp.ldon_points_mir < (0-mirpts)) + { + mmcpts+=mirpts+m_pp.ldon_points_mir; + mirpts=0-m_pp.ldon_points_mir; + } + if(m_pp.ldon_points_mmc < (0-mmcpts)) + { + rujpts+=mmcpts+m_pp.ldon_points_mmc; + mmcpts=0-m_pp.ldon_points_mmc; + } + if(m_pp.ldon_points_ruj < (0-rujpts)) + { + takpts+=rujpts+m_pp.ldon_points_ruj; + rujpts=0-m_pp.ldon_points_ruj; + } + if(m_pp.ldon_points_tak < (0-takpts)) + { + splitpts=takpts+m_pp.ldon_points_tak; + takpts=0-m_pp.ldon_points_tak; + } + } + m_pp.ldon_points_guk += gukpts; + m_pp.ldon_points_mir+=mirpts; + m_pp.ldon_points_mmc += mmcpts; + m_pp.ldon_points_ruj += rujpts; + m_pp.ldon_points_tak += takpts; + points-=splitpts; + // if anything left, recursively loop thru again + if (splitpts !=0) + UpdateLDoNPoints(splitpts,0); + break; + } + case 1: + { + if(points < 0) + { + if(m_pp.ldon_points_guk < (0-points)) + return false; + } + m_pp.ldon_points_guk += points; + break; + } + case 2: + { + if(points < 0) + { + if(m_pp.ldon_points_mir < (0-points)) + return false; + } + m_pp.ldon_points_mir += points; + break; + } + case 3: + { + if(points < 0) + { + if(m_pp.ldon_points_mmc < (0-points)) + return false; + } + m_pp.ldon_points_mmc += points; + break; + } + case 4: + { + if(points < 0) + { + if(m_pp.ldon_points_ruj < (0-points)) + return false; + } + m_pp.ldon_points_ruj += points; + break; + } + case 5: + { + if(points < 0) + { + if(m_pp.ldon_points_tak < (0-points)) + return false; + } + m_pp.ldon_points_tak += points; + break; + } + } + m_pp.ldon_points_available += points; + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AdventurePointsUpdate, sizeof(AdventurePoints_Update_Struct)); + AdventurePoints_Update_Struct* apus = (AdventurePoints_Update_Struct*)outapp->pBuffer; + apus->ldon_available_points = m_pp.ldon_points_available; + apus->ldon_guk_points = m_pp.ldon_points_guk; + apus->ldon_mirugal_points = m_pp.ldon_points_mir; + apus->ldon_mistmoore_points = m_pp.ldon_points_mmc; + apus->ldon_rujarkian_points = m_pp.ldon_points_ruj; + apus->ldon_takish_points = m_pp.ldon_points_tak; + outapp->priority = 6; + QueuePacket(outapp); + safe_delete(outapp); + return true; + + return(false); +} + +void Client::SetSkill(SkillUseTypes skillid, uint16 value) { + if (skillid > HIGHEST_SKILL) + return; + m_pp.skills[skillid] = value; // We need to be able to #setskill 254 and 255 to reset skills + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SkillUpdate, sizeof(SkillUpdate_Struct)); + SkillUpdate_Struct* skill = (SkillUpdate_Struct*)outapp->pBuffer; + skill->skillId=skillid; + skill->value=value; + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::IncreaseLanguageSkill(int skill_id, int value) { + + if (skill_id >= MAX_PP_LANGUAGE) + return; //Invalid lang id + + m_pp.languages[skill_id] += value; + + if (m_pp.languages[skill_id] > 100) //Lang skill above max + m_pp.languages[skill_id] = 100; + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SkillUpdate, sizeof(SkillUpdate_Struct)); + SkillUpdate_Struct* skill = (SkillUpdate_Struct*)outapp->pBuffer; + skill->skillId = 100 + skill_id; + skill->value = m_pp.languages[skill_id]; + QueuePacket(outapp); + safe_delete(outapp); + + Message_StringID( MT_Skills, LANG_SKILL_IMPROVED ); //Notify client +} + +void Client::AddSkill(SkillUseTypes skillid, uint16 value) { + if (skillid > HIGHEST_SKILL) + return; + value = GetRawSkill(skillid) + value; + uint16 max = GetMaxSkillAfterSpecializationRules(skillid, MaxSkill(skillid)); + if (value > max) + value = max; + SetSkill(skillid, value); +} + +void Client::SendSound(){//Makes a sound. + EQApplicationPacket* outapp = new EQApplicationPacket(OP_Sound, 68); + unsigned char x[68]; + memset(x, 0, 68); + x[0]=0x22; + memset(&x[4],0x8002,sizeof(uint16)); + memset(&x[8],0x8624,sizeof(uint16)); + memset(&x[12],0x4A01,sizeof(uint16)); + x[16]=0x05; + x[28]=0x00;//change this value to give gold to the client + memset(&x[40],0xFFFFFFFF,sizeof(uint32)); + memset(&x[44],0xFFFFFFFF,sizeof(uint32)); + memset(&x[48],0xFFFFFFFF,sizeof(uint32)); + memset(&x[52],0xFFFFFFFF,sizeof(uint32)); + memset(&x[56],0xFFFFFFFF,sizeof(uint32)); + memset(&x[60],0xFFFFFFFF,sizeof(uint32)); + memset(&x[64],0xffffffff,sizeof(uint32)); + memcpy(outapp->pBuffer,x,outapp->size); + QueuePacket(outapp); + safe_delete(outapp); + +} +void Client::UpdateWho(uint8 remove) { + if (account_id == 0) + return; + if (!worldserver.Connected()) + return; + ServerPacket* pack = new ServerPacket(ServerOP_ClientList, sizeof(ServerClientList_Struct)); + ServerClientList_Struct* scl = (ServerClientList_Struct*) pack->pBuffer; + scl->remove = remove; + scl->wid = this->GetWID(); + scl->IP = this->GetIP(); + scl->charid = this->CharacterID(); + strcpy(scl->name, this->GetName()); + + scl->gm = GetGM(); + scl->Admin = this->Admin(); + scl->AccountID = this->AccountID(); + strcpy(scl->AccountName, this->AccountName()); + scl->LSAccountID = this->LSAccountID(); + strn0cpy(scl->lskey, lskey, sizeof(scl->lskey)); + scl->zone = zone->GetZoneID(); + scl->instance_id = zone->GetInstanceID(); + scl->race = this->GetRace(); + scl->class_ = GetClass(); + scl->level = GetLevel(); + if (m_pp.anon == 0) + scl->anon = 0; + else if (m_pp.anon == 1) + scl->anon = 1; + else if (m_pp.anon >= 2) + scl->anon = 2; + + scl->ClientVersion = GetClientVersion(); + scl->tellsoff = tellsoff; + scl->guild_id = guild_id; + scl->LFG = LFG; + if(LFG) { + scl->LFGFromLevel = LFGFromLevel; + scl->LFGToLevel = LFGToLevel; + scl->LFGMatchFilter = LFGMatchFilter; + memcpy(scl->LFGComments, LFGComments, sizeof(scl->LFGComments)); + } + + worldserver.SendPacket(pack); + safe_delete(pack); +} + +void Client::WhoAll(Who_All_Struct* whom) { + + if (!worldserver.Connected()) + Message(0, "Error: World server disconnected"); + else { + ServerPacket* pack = new ServerPacket(ServerOP_Who, sizeof(ServerWhoAll_Struct)); + ServerWhoAll_Struct* whoall = (ServerWhoAll_Struct*) pack->pBuffer; + whoall->admin = this->Admin(); + whoall->fromid=this->GetID(); + strcpy(whoall->from, this->GetName()); + strn0cpy(whoall->whom, whom->whom, 64); + whoall->lvllow = whom->lvllow; + whoall->lvlhigh = whom->lvlhigh; + whoall->gmlookup = whom->gmlookup; + whoall->wclass = whom->wclass; + whoall->wrace = whom->wrace; + worldserver.SendPacket(pack); + safe_delete(pack); + } +} + +void Client::FriendsWho(char *FriendsString) { + + if (!worldserver.Connected()) + Message(0, "Error: World server disconnected"); + else { + ServerPacket* pack = new ServerPacket(ServerOP_FriendsWho, sizeof(ServerFriendsWho_Struct) + strlen(FriendsString)); + ServerFriendsWho_Struct* FriendsWho = (ServerFriendsWho_Struct*) pack->pBuffer; + FriendsWho->FromID = this->GetID(); + strcpy(FriendsWho->FromName, GetName()); + strcpy(FriendsWho->FriendsString, FriendsString); + worldserver.SendPacket(pack); + safe_delete(pack); + } +} + + +void Client::UpdateAdmin(bool iFromDB) { + int16 tmp = admin; + if (iFromDB) + admin = database.CheckStatus(account_id); + if (tmp == admin && iFromDB) + return; + + if(m_pp.gm) + { +#if EQDEBUG >= 5 + printf("%s is a GM\n", GetName()); +#endif +// no need for this, having it set in pp you already start as gm +// and it's also set in your spawn packet so other people see it too +// SendAppearancePacket(AT_GM, 1, false); + petition_list.UpdateGMQueue(); + } + + UpdateWho(); +} + +void Client::SetStats(uint8 type,int16 set_val){ + if(type>STAT_DISEASE){ + printf("Error in Client::IncStats, received invalid type of: %i\n",type); + return; + } + EQApplicationPacket* outapp = new EQApplicationPacket(OP_IncreaseStats,sizeof(IncreaseStat_Struct)); + IncreaseStat_Struct* iss=(IncreaseStat_Struct*)outapp->pBuffer; + switch(type){ + case STAT_STR: + if(set_val>0) + iss->str=set_val; + if(set_val<0) + m_pp.STR=0; + else if(set_val>255) + m_pp.STR=255; + else + m_pp.STR=set_val; + break; + case STAT_STA: + if(set_val>0) + iss->sta=set_val; + if(set_val<0) + m_pp.STA=0; + else if(set_val>255) + m_pp.STA=255; + else + m_pp.STA=set_val; + break; + case STAT_AGI: + if(set_val>0) + iss->agi=set_val; + if(set_val<0) + m_pp.AGI=0; + else if(set_val>255) + m_pp.AGI=255; + else + m_pp.AGI=set_val; + break; + case STAT_DEX: + if(set_val>0) + iss->dex=set_val; + if(set_val<0) + m_pp.DEX=0; + else if(set_val>255) + m_pp.DEX=255; + else + m_pp.DEX=set_val; + break; + case STAT_INT: + if(set_val>0) + iss->int_=set_val; + if(set_val<0) + m_pp.INT=0; + else if(set_val>255) + m_pp.INT=255; + else + m_pp.INT=set_val; + break; + case STAT_WIS: + if(set_val>0) + iss->wis=set_val; + if(set_val<0) + m_pp.WIS=0; + else if(set_val>255) + m_pp.WIS=255; + else + m_pp.WIS=set_val; + break; + case STAT_CHA: + if(set_val>0) + iss->cha=set_val; + if(set_val<0) + m_pp.CHA=0; + else if(set_val>255) + m_pp.CHA=255; + else + m_pp.CHA=set_val; + break; + } + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::IncStats(uint8 type,int16 increase_val){ + if(type>STAT_DISEASE){ + printf("Error in Client::IncStats, received invalid type of: %i\n",type); + return; + } + EQApplicationPacket* outapp = new EQApplicationPacket(OP_IncreaseStats,sizeof(IncreaseStat_Struct)); + IncreaseStat_Struct* iss=(IncreaseStat_Struct*)outapp->pBuffer; + switch(type){ + case STAT_STR: + if(increase_val>0) + iss->str=increase_val; + if((m_pp.STR+increase_val*2)<0) + m_pp.STR=0; + else if((m_pp.STR+increase_val*2)>255) + m_pp.STR=255; + else + m_pp.STR+=increase_val*2; + break; + case STAT_STA: + if(increase_val>0) + iss->sta=increase_val; + if((m_pp.STA+increase_val*2)<0) + m_pp.STA=0; + else if((m_pp.STA+increase_val*2)>255) + m_pp.STA=255; + else + m_pp.STA+=increase_val*2; + break; + case STAT_AGI: + if(increase_val>0) + iss->agi=increase_val; + if((m_pp.AGI+increase_val*2)<0) + m_pp.AGI=0; + else if((m_pp.AGI+increase_val*2)>255) + m_pp.AGI=255; + else + m_pp.AGI+=increase_val*2; + break; + case STAT_DEX: + if(increase_val>0) + iss->dex=increase_val; + if((m_pp.DEX+increase_val*2)<0) + m_pp.DEX=0; + else if((m_pp.DEX+increase_val*2)>255) + m_pp.DEX=255; + else + m_pp.DEX+=increase_val*2; + break; + case STAT_INT: + if(increase_val>0) + iss->int_=increase_val; + if((m_pp.INT+increase_val*2)<0) + m_pp.INT=0; + else if((m_pp.INT+increase_val*2)>255) + m_pp.INT=255; + else + m_pp.INT+=increase_val*2; + break; + case STAT_WIS: + if(increase_val>0) + iss->wis=increase_val; + if((m_pp.WIS+increase_val*2)<0) + m_pp.WIS=0; + else if((m_pp.WIS+increase_val*2)>255) + m_pp.WIS=255; + else + m_pp.WIS+=increase_val*2; + break; + case STAT_CHA: + if(increase_val>0) + iss->cha=increase_val; + if((m_pp.CHA+increase_val*2)<0) + m_pp.CHA=0; + else if((m_pp.CHA+increase_val*2)>255) + m_pp.CHA=255; + else + m_pp.CHA+=increase_val*2; + break; + } + QueuePacket(outapp); + safe_delete(outapp); +} + +const int32& Client::SetMana(int32 amount) { + bool update = false; + if (amount < 0) + amount = 0; + if (amount > GetMaxMana()) + amount = GetMaxMana(); + if (amount != cur_mana) + update = true; + cur_mana = amount; + if (update) + Mob::SetMana(amount); + SendManaUpdatePacket(); + return cur_mana; +} + +void Client::SendManaUpdatePacket() { + if (!Connected() || IsCasting()) + return; + + if (GetClientVersion() >= EQClientSoD) { + SendManaUpdate(); + SendEnduranceUpdate(); + } + + //std::cout << "Sending mana update: " << (cur_mana - last_reported_mana) << std::endl; + if (last_reported_mana != cur_mana || last_reported_endur != cur_end) { + + + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_ManaChange, sizeof(ManaChange_Struct)); + ManaChange_Struct* manachange = (ManaChange_Struct*)outapp->pBuffer; + manachange->new_mana = cur_mana; + manachange->stamina = cur_end; + manachange->spell_id = casting_spell_id; //always going to be 0... since we check IsCasting() + outapp->priority = 6; + QueuePacket(outapp); + safe_delete(outapp); + + Group *g = GetGroup(); + + if(g) + { + outapp = new EQApplicationPacket(OP_MobManaUpdate, sizeof(MobManaUpdate_Struct)); + EQApplicationPacket *outapp2 = new EQApplicationPacket(OP_MobEnduranceUpdate, sizeof(MobEnduranceUpdate_Struct)); + + MobManaUpdate_Struct *mmus = (MobManaUpdate_Struct *)outapp->pBuffer; + MobEnduranceUpdate_Struct *meus = (MobEnduranceUpdate_Struct *)outapp2->pBuffer; + + mmus->spawn_id = meus->spawn_id = GetID(); + + mmus->mana = GetManaPercent(); + meus->endurance = GetEndurancePercent(); + + + for(int i = 0; i < MAX_GROUP_MEMBERS; ++i) + if(g->members[i] && g->members[i]->IsClient() && (g->members[i] != this) && (g->members[i]->CastToClient()->GetClientVersion() >= EQClientSoD)) + { + g->members[i]->CastToClient()->QueuePacket(outapp); + g->members[i]->CastToClient()->QueuePacket(outapp2); + } + + safe_delete(outapp); + safe_delete(outapp2); + } + + + last_reported_mana = cur_mana; + last_reported_endur = cur_end; + } +} + +// sends mana update to self +void Client::SendManaUpdate() +{ + EQApplicationPacket* mana_app = new EQApplicationPacket(OP_ManaUpdate,sizeof(ManaUpdate_Struct)); + ManaUpdate_Struct* mus = (ManaUpdate_Struct*)mana_app->pBuffer; + mus->cur_mana = GetMana(); + mus->max_mana = GetMaxMana(); + mus->spawn_id = GetID(); + QueuePacket(mana_app); + entity_list.QueueClientsByXTarget(this, mana_app, false); + safe_delete(mana_app); +} + +// sends endurance update to self +void Client::SendEnduranceUpdate() +{ + EQApplicationPacket* end_app = new EQApplicationPacket(OP_EnduranceUpdate,sizeof(EnduranceUpdate_Struct)); + EnduranceUpdate_Struct* eus = (EnduranceUpdate_Struct*)end_app->pBuffer; + eus->cur_end = GetEndurance(); + eus->max_end = GetMaxEndurance(); + eus->spawn_id = GetID(); + QueuePacket(end_app); + entity_list.QueueClientsByXTarget(this, end_app, false); + safe_delete(end_app); +} + +void Client::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) +{ + Mob::FillSpawnStruct(ns, ForWho); + + // Populate client-specific spawn information + ns->spawn.afk = AFK; + ns->spawn.lfg = LFG; // afk and lfg are cleared on zoning on live + ns->spawn.anon = m_pp.anon; + ns->spawn.gm = GetGM() ? 1 : 0; + ns->spawn.guildID = GuildID(); +// ns->spawn.linkdead = IsLD() ? 1 : 0; +// ns->spawn.pvp = GetPVP() ? 1 : 0; + + + strcpy(ns->spawn.title, m_pp.title); + strcpy(ns->spawn.suffix, m_pp.suffix); + + if (IsBecomeNPC() == true) + ns->spawn.NPC = 1; + else if (ForWho == this) + ns->spawn.NPC = 10; + else + ns->spawn.NPC = 0; + ns->spawn.is_pet = 0; + + if (!IsInAGuild()) { + ns->spawn.guildrank = 0xFF; + } else { + ns->spawn.guildrank = guild_mgr.GetDisplayedRank(GuildID(), GuildRank(), AccountID()); + } + ns->spawn.size = 0; // Changing size works, but then movement stops! (wth?) + ns->spawn.runspeed = (gmspeed == 0) ? runspeed : 3.125f; + if (!m_pp.showhelm) ns->spawn.showhelm = 0; + + // pp also hold this info; should we pull from there or inventory? + // (update: i think pp should do it, as this holds LoY dye - plus, this is ugly code with Inventory!) + const Item_Struct* item = nullptr; + const ItemInst* inst = nullptr; + if ((inst = m_inv[MainHands]) && inst->IsType(ItemClassCommon)) { + item = inst->GetItem(); + ns->spawn.equipment[MaterialHands] = item->Material; + ns->spawn.colors[MaterialHands].color = GetEquipmentColor(MaterialHands); + } + if ((inst = m_inv[MainHead]) && inst->IsType(ItemClassCommon)) { + item = inst->GetItem(); + ns->spawn.equipment[MaterialHead] = item->Material; + ns->spawn.colors[MaterialHead].color = GetEquipmentColor(MaterialHead); + } + if ((inst = m_inv[MainArms]) && inst->IsType(ItemClassCommon)) { + item = inst->GetItem(); + ns->spawn.equipment[MaterialArms] = item->Material; + ns->spawn.colors[MaterialArms].color = GetEquipmentColor(MaterialArms); + } + if ((inst = m_inv[MainWrist1]) && inst->IsType(ItemClassCommon)) { + item = inst->GetItem(); + ns->spawn.equipment[MaterialWrist]= item->Material; + ns->spawn.colors[MaterialWrist].color = GetEquipmentColor(MaterialWrist); + } + + /* + // non-live behavior + if ((inst = m_inv[SLOT_BRACER02]) && inst->IsType(ItemClassCommon)) { + item = inst->GetItem(); + ns->spawn.equipment[MaterialWrist]= item->Material; + ns->spawn.colors[MaterialWrist].color = GetEquipmentColor(MaterialWrist); + } + */ + + if ((inst = m_inv[MainChest]) && inst->IsType(ItemClassCommon)) { + item = inst->GetItem(); + ns->spawn.equipment[MaterialChest] = item->Material; + ns->spawn.colors[MaterialChest].color = GetEquipmentColor(MaterialChest); + } + if ((inst = m_inv[MainLegs]) && inst->IsType(ItemClassCommon)) { + item = inst->GetItem(); + ns->spawn.equipment[MaterialLegs] = item->Material; + ns->spawn.colors[MaterialLegs].color = GetEquipmentColor(MaterialLegs); + } + if ((inst = m_inv[MainFeet]) && inst->IsType(ItemClassCommon)) { + item = inst->GetItem(); + ns->spawn.equipment[MaterialFeet] = item->Material; + ns->spawn.colors[MaterialFeet].color = GetEquipmentColor(MaterialFeet); + } + if ((inst = m_inv[MainPrimary]) && inst->IsType(ItemClassCommon)) { + item = inst->GetItem(); + if (strlen(item->IDFile) > 2) + ns->spawn.equipment[MaterialPrimary] = atoi(&item->IDFile[2]); + } + if ((inst = m_inv[MainSecondary]) && inst->IsType(ItemClassCommon)) { + item = inst->GetItem(); + if (strlen(item->IDFile) > 2) + ns->spawn.equipment[MaterialSecondary] = atoi(&item->IDFile[2]); + } + + //these two may be related to ns->spawn.texture + /* + ns->spawn.npc_armor_graphic = texture; + ns->spawn.npc_helm_graphic = helmtexture; + */ + + //filling in some unknowns to make the client happy +// ns->spawn.unknown0002[2] = 3; + +} + +bool Client::GMHideMe(Client* client) { + if (gmhideme) { + if (client == 0) + return true; + else if (admin > client->Admin()) + return true; + else + return false; + } + else + return false; +} + +void Client::Duck() { + SetAppearance(eaCrouching, false); +} + +void Client::Stand() { + SetAppearance(eaStanding, false); +} + +void Client::ChangeLastName(const char* in_lastname) { + memset(m_pp.last_name, 0, sizeof(m_pp.last_name)); + strn0cpy(m_pp.last_name, in_lastname, sizeof(m_pp.last_name)); + EQApplicationPacket* outapp = new EQApplicationPacket(OP_GMLastName, sizeof(GMLastName_Struct)); + GMLastName_Struct* gmn = (GMLastName_Struct*)outapp->pBuffer; + strcpy(gmn->name, name); + strcpy(gmn->gmname, name); + strcpy(gmn->lastname, in_lastname); + gmn->unknown[0]=1; + gmn->unknown[1]=1; + gmn->unknown[2]=1; + gmn->unknown[3]=1; + entity_list.QueueClients(this, outapp, false); + // Send name update packet here... once know what it is + safe_delete(outapp); +} + +bool Client::ChangeFirstName(const char* in_firstname, const char* gmname) +{ + // check duplicate name + bool usedname = database.CheckUsedName((const char*) in_firstname); + if (!usedname) { + return false; + } + + // update character_ + if(!database.UpdateName(GetName(), in_firstname)) + return false; + + // update pp + memset(m_pp.name, 0, sizeof(m_pp.name)); + snprintf(m_pp.name, sizeof(m_pp.name), "%s", in_firstname); + strcpy(name, m_pp.name); + Save(); + + // send name update packet + EQApplicationPacket* outapp = new EQApplicationPacket(OP_GMNameChange, sizeof(GMName_Struct)); + GMName_Struct* gmn=(GMName_Struct*)outapp->pBuffer; + strn0cpy(gmn->gmname,gmname,64); + strn0cpy(gmn->oldname,GetName(),64); + strn0cpy(gmn->newname,in_firstname,64); + gmn->unknown[0] = 1; + gmn->unknown[1] = 1; + gmn->unknown[2] = 1; + entity_list.QueueClients(this, outapp, false); + safe_delete(outapp); + + // finally, update the /who list + UpdateWho(); + + // success + return true; +} + +void Client::SetGM(bool toggle) { + m_pp.gm = toggle ? 1 : 0; + Message(13, "You are %s a GM.", m_pp.gm ? "now" : "no longer"); + SendAppearancePacket(AT_GM, m_pp.gm); + Save(); + UpdateWho(); +} + +void Client::ReadBook(BookRequest_Struct *book) { + char *txtfile = book->txtfile; + + if(txtfile[0] == '0' && txtfile[1] == '\0') { + //invalid book... coming up on non-book items. + return; + } + + std::string booktxt2 = database.GetBook(txtfile); + int length = booktxt2.length(); + + if (booktxt2[0] != '\0') { +#if EQDEBUG >= 6 + LogFile->write(EQEMuLog::Normal,"Client::ReadBook() textfile:%s Text:%s", txtfile, booktxt2.c_str()); +#endif + EQApplicationPacket* outapp = new EQApplicationPacket(OP_ReadBook, length + sizeof(BookText_Struct)); + + BookText_Struct *out = (BookText_Struct *) outapp->pBuffer; + out->window = book->window; + if(GetClientVersion() >= EQClientSoF) + { + const ItemInst *inst = m_inv[book->invslot]; + if(inst) + out->type = inst->GetItem()->Book; + else + out->type = book->type; + } + else + { + out->type = book->type; + } + out->invslot = book->invslot; + memcpy(out->booktext, booktxt2.c_str(), length); + + QueuePacket(outapp); + safe_delete(outapp); + } +} + +void Client::QuestReadBook(const char* text, uint8 type) { + std::string booktxt2 = text; + int length = booktxt2.length(); + if (booktxt2[0] != '\0') { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_ReadBook, length + sizeof(BookText_Struct)); + BookText_Struct *out = (BookText_Struct *) outapp->pBuffer; + out->window = 0xFF; + out->type = type; + out->invslot = 0; + memcpy(out->booktext, booktxt2.c_str(), length); + QueuePacket(outapp); + safe_delete(outapp); + } +} + +void Client::SendClientMoneyUpdate(uint8 type,uint32 amount){ + EQApplicationPacket* outapp = new EQApplicationPacket(OP_TradeMoneyUpdate,sizeof(TradeMoneyUpdate_Struct)); + TradeMoneyUpdate_Struct* mus= (TradeMoneyUpdate_Struct*)outapp->pBuffer; + mus->amount=amount; + mus->trader=0; + mus->type=type; + QueuePacket(outapp); + safe_delete(outapp); +} + +bool Client::TakeMoneyFromPP(uint64 copper, bool updateclient) { + int64 copperpp,silver,gold,platinum; + copperpp = m_pp.copper; + silver = static_cast(m_pp.silver) * 10; + gold = static_cast(m_pp.gold) * 100; + platinum = static_cast(m_pp.platinum) * 1000; + + int64 clienttotal = copperpp + silver + gold + platinum; + + clienttotal -= copper; + if(clienttotal < 0) + { + return false; // Not enough money! + } + else + { + copperpp -= copper; + if(copperpp <= 0) + { + copper = abs64(copperpp); + m_pp.copper = 0; + } + else + { + m_pp.copper = copperpp; + if(updateclient) + SendMoneyUpdate(); + Save(); + return true; + } + silver -= copper; + if(silver <= 0) + { + copper = abs64(silver); + m_pp.silver = 0; + } + else + { + m_pp.silver = silver/10; + m_pp.copper += (silver-(m_pp.silver*10)); + if(updateclient) + SendMoneyUpdate(); + Save(); + return true; + } + + gold -=copper; + + if(gold <= 0) + { + copper = abs64(gold); + m_pp.gold = 0; + } + else + { + m_pp.gold = gold/100; + uint64 silvertest = (gold-(static_cast(m_pp.gold)*100))/10; + m_pp.silver += silvertest; + uint64 coppertest = (gold-(static_cast(m_pp.gold)*100+silvertest*10)); + m_pp.copper += coppertest; + if(updateclient) + SendMoneyUpdate(); + Save(); + return true; + } + + platinum -= copper; + + //Impossible for plat to be negative, already checked above + + m_pp.platinum = platinum/1000; + uint64 goldtest = (platinum-(static_cast(m_pp.platinum)*1000))/100; + m_pp.gold += goldtest; + uint64 silvertest = (platinum-(static_cast(m_pp.platinum)*1000+goldtest*100))/10; + m_pp.silver += silvertest; + uint64 coppertest = (platinum-(static_cast(m_pp.platinum)*1000+goldtest*100+silvertest*10)); + m_pp.copper = coppertest; + if(updateclient) + SendMoneyUpdate(); + RecalcWeight(); + Save(); + return true; + } +} + +void Client::AddMoneyToPP(uint64 copper, bool updateclient){ + uint64 tmp; + uint64 tmp2; + tmp = copper; + + // Add Amount of Platinum + tmp2 = tmp/1000; + int32 new_val = m_pp.platinum + tmp2; + if(new_val < 0) { + m_pp.platinum = 0; + } else { + m_pp.platinum = m_pp.platinum + tmp2; + } + tmp-=tmp2*1000; + + //if (updateclient) + // SendClientMoneyUpdate(3,tmp2); + + // Add Amount of Gold + tmp2 = tmp/100; + new_val = m_pp.gold + tmp2; + if(new_val < 0) { + m_pp.gold = 0; + } else { + m_pp.gold = m_pp.gold + tmp2; + } + tmp-=tmp2*100; + //if (updateclient) + // SendClientMoneyUpdate(2,tmp2); + + // Add Amount of Silver + tmp2 = tmp/10; + new_val = m_pp.silver + tmp2; + if(new_val < 0) { + m_pp.silver = 0; + } else { + m_pp.silver = m_pp.silver + tmp2; + } + tmp-=tmp2*10; + //if (updateclient) + // SendClientMoneyUpdate(1,tmp2); + + // Add Copper + //tmp = tmp - (tmp2* 10); + //if (updateclient) + // SendClientMoneyUpdate(0,tmp); + tmp2 = tmp; + new_val = m_pp.copper + tmp2; + if(new_val < 0) { + m_pp.copper = 0; + } else { + m_pp.copper = m_pp.copper + tmp2; + } + + + //send them all at once, since the above code stopped working. + if(updateclient) + SendMoneyUpdate(); + + RecalcWeight(); + + Save(); + + LogFile->write(EQEMuLog::Debug, "Client::AddMoneyToPP() %s should have: plat:%i gold:%i silver:%i copper:%i", GetName(), m_pp.platinum, m_pp.gold, m_pp.silver, m_pp.copper); +} + +void Client::AddMoneyToPP(uint32 copper, uint32 silver, uint32 gold, uint32 platinum, bool updateclient){ + + /* Set a timestamp in an entity variable for plugin check_handin.pl in return_items + This will stopgap players from items being returned if global_npc.pl has a catch all return_items + */ + struct timeval read_time; + char buffer[50]; + gettimeofday(&read_time, 0); + sprintf(buffer, "%li.%li \n", read_time.tv_sec, read_time.tv_usec); + this->SetEntityVariable("Stop_Return", buffer); + + int32 new_value = m_pp.platinum + platinum; + if(new_value >= 0 && new_value > m_pp.platinum) + m_pp.platinum += platinum; + + new_value = m_pp.gold + gold; + if(new_value >= 0 && new_value > m_pp.gold) + m_pp.gold += gold; + + new_value = m_pp.silver + silver; + if(new_value >= 0 && new_value > m_pp.silver) + m_pp.silver += silver; + + new_value = m_pp.copper + copper; + if(new_value >= 0 && new_value > m_pp.copper) + m_pp.copper += copper; + + if(updateclient) + SendMoneyUpdate(); + + RecalcWeight(); + Save(); + +#if (EQDEBUG>=5) + LogFile->write(EQEMuLog::Debug, "Client::AddMoneyToPP() %s should have: plat:%i gold:%i silver:%i copper:%i", + GetName(), m_pp.platinum, m_pp.gold, m_pp.silver, m_pp.copper); +#endif +} + +void Client::SendMoneyUpdate() { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_MoneyUpdate,sizeof(MoneyUpdate_Struct)); + MoneyUpdate_Struct* mus= (MoneyUpdate_Struct*)outapp->pBuffer; + + mus->platinum = m_pp.platinum; + mus->gold = m_pp.gold; + mus->silver = m_pp.silver; + mus->copper = m_pp.copper; + + FastQueuePacket(&outapp); +} + +bool Client::HasMoney(uint64 Copper) { + + if ((static_cast(m_pp.copper) + + (static_cast(m_pp.silver) * 10) + + (static_cast(m_pp.gold) * 100) + + (static_cast(m_pp.platinum) * 1000)) >= Copper) + return true; + + return false; +} + +uint64 Client::GetCarriedMoney() { + + return ((static_cast(m_pp.copper) + + (static_cast(m_pp.silver) * 10) + + (static_cast(m_pp.gold) * 100) + + (static_cast(m_pp.platinum) * 1000))); +} + +uint64 Client::GetAllMoney() { + + return ( + (static_cast(m_pp.copper) + + (static_cast(m_pp.silver) * 10) + + (static_cast(m_pp.gold) * 100) + + (static_cast(m_pp.platinum) * 1000) + + (static_cast(m_pp.copper_bank) + + (static_cast(m_pp.silver_bank) * 10) + + (static_cast(m_pp.gold_bank) * 100) + + (static_cast(m_pp.platinum_bank) * 1000) + + (static_cast(m_pp.copper_cursor) + + (static_cast(m_pp.silver_cursor) * 10) + + (static_cast(m_pp.gold_cursor) * 100) + + (static_cast(m_pp.platinum_cursor) * 1000) + + (static_cast(m_pp.platinum_shared) * 1000))))); +} + +bool Client::CheckIncreaseSkill(SkillUseTypes skillid, Mob *against_who, int chancemodi) { + if (IsDead() || IsUnconscious()) + return false; + if (IsAIControlled()) // no skillups while chamred =p + return false; + if (skillid > HIGHEST_SKILL) + return false; + int skillval = GetRawSkill(skillid); + int maxskill = GetMaxSkillAfterSpecializationRules(skillid, MaxSkill(skillid)); + + if(against_who) + { + if(against_who->GetSpecialAbility(IMMUNE_AGGRO) || against_who->IsClient() || + GetLevelCon(against_who->GetLevel()) == CON_GREEN) + { + //false by default + if( !mod_can_increase_skill(skillid, against_who) ) { return(false); } + } + } + + // Make sure we're not already at skill cap + if (skillval < maxskill) + { + // the higher your current skill level, the harder it is + int16 Chance = 10 + chancemodi + ((252 - skillval) / 20); + if (Chance < 1) + Chance = 1; // Make it always possible + Chance = (Chance * RuleI(Character, SkillUpModifier) / 100); + + Chance = mod_increase_skill_chance(Chance, against_who); + + if(MakeRandomFloat(0, 99) < Chance) + { + SetSkill(skillid, GetRawSkill(skillid) + 1); + _log(SKILLS__GAIN, "Skill %d at value %d successfully gain with %.4f%%chance (mod %d)", skillid, skillval, Chance, chancemodi); + return true; + } else { + _log(SKILLS__GAIN, "Skill %d at value %d failed to gain with %.4f%%chance (mod %d)", skillid, skillval, Chance, chancemodi); + } + } else { + _log(SKILLS__GAIN, "Skill %d at value %d cannot increase due to maxmum %d", skillid, skillval, maxskill); + } + return false; +} + +void Client::CheckLanguageSkillIncrease(uint8 langid, uint8 TeacherSkill) { + if (IsDead() || IsUnconscious()) + return; + if (IsAIControlled()) + return; + if (langid >= MAX_PP_LANGUAGE) + return; // do nothing if langid is an invalid language + + int LangSkill = m_pp.languages[langid]; // get current language skill + + if (LangSkill < 100) { // if the language isn't already maxed + int16 Chance = 5 + ((TeacherSkill - LangSkill)/10); // greater chance to learn if teacher's skill is much higher than yours + Chance = (Chance * RuleI(Character, SkillUpModifier)/100); + + if(MakeRandomFloat(0,100) < Chance) { // if they make the roll + IncreaseLanguageSkill(langid); // increase the language skill by 1 + _log(SKILLS__GAIN, "Language %d at value %d successfully gain with %.4f%%chance", langid, LangSkill, Chance); + } + else + _log(SKILLS__GAIN, "Language %d at value %d failed to gain with %.4f%%chance", langid, LangSkill, Chance); + } +} + +bool Client::HasSkill(SkillUseTypes skill_id) const { + return((GetSkill(skill_id) > 0) && CanHaveSkill(skill_id)); +} + +bool Client::CanHaveSkill(SkillUseTypes skill_id) const { + return(database.GetSkillCap(GetClass(), skill_id, RuleI(Character, MaxLevel)) > 0); + //if you don't have it by max level, then odds are you never will? +} + +uint16 Client::MaxSkill(SkillUseTypes skillid, uint16 class_, uint16 level) const { + return(database.GetSkillCap(class_, skillid, level)); +} + +uint8 Client::SkillTrainLevel(SkillUseTypes skillid, uint16 class_){ + return(database.GetTrainLevel(class_, skillid, RuleI(Character, MaxLevel))); +} + +uint16 Client::GetMaxSkillAfterSpecializationRules(SkillUseTypes skillid, uint16 maxSkill) +{ + uint16 Result = maxSkill; + + uint16 PrimarySpecialization = 0, SecondaryForte = 0; + + uint16 PrimarySkillValue = 0, SecondarySkillValue = 0; + + uint16 MaxSpecializations = GetAA(aaSecondaryForte) ? 2 : 1; + + if(skillid >= SkillSpecializeAbjure && skillid <= SkillSpecializeEvocation) + { + bool HasPrimarySpecSkill = false; + + int NumberOfPrimarySpecSkills = 0; + + for(int i = SkillSpecializeAbjure; i <= SkillSpecializeEvocation; ++i) + { + if(m_pp.skills[i] > 50) + { + HasPrimarySpecSkill = true; + NumberOfPrimarySpecSkills++; + } + if(m_pp.skills[i] > PrimarySkillValue) + { + if(PrimarySkillValue > SecondarySkillValue) + { + SecondarySkillValue = PrimarySkillValue; + SecondaryForte = PrimarySpecialization; + } + + PrimarySpecialization = i; + PrimarySkillValue = m_pp.skills[i]; + } + else if(m_pp.skills[i] > SecondarySkillValue) + { + SecondaryForte = i; + SecondarySkillValue = m_pp.skills[i]; + } + } + + if(SecondarySkillValue <=50) + SecondaryForte = 0; + + if(HasPrimarySpecSkill) + { + if(NumberOfPrimarySpecSkills <= MaxSpecializations) + { + if(MaxSpecializations == 1) + { + if(skillid != PrimarySpecialization) + { + Result = 50; + } + } + else + { + if((skillid != PrimarySpecialization) && ((skillid == SecondaryForte) || (SecondaryForte == 0))) + { + if((PrimarySkillValue > 100) || (!SecondaryForte)) + Result = 100; + } + else if(skillid != PrimarySpecialization) + { + Result = 50; + } + } + } + else + { + Message(13, "Your spell casting specializations skills have been reset. " + "Only %i primary specialization skill is allowed.", MaxSpecializations); + + for(int i = SkillSpecializeAbjure; i <= SkillSpecializeEvocation; ++i) + SetSkill((SkillUseTypes)i, 1); + + Save(); + + LogFile->write(EQEMuLog::Normal, "Reset %s's caster specialization skills to 1. " + "Too many specializations skills were above 50.", GetCleanName()); + } + + } + } + // This should possibly be handled by bonuses rather than here. + switch(skillid) + { + case SkillTracking: + { + Result += ((GetAA(aaAdvancedTracking) * 10) + (GetAA(aaTuneofPursuance) * 10)); + break; + } + + default: + break; + } + + return Result; +} + +void Client::SetPVP(bool toggle) { + m_pp.pvp = toggle ? 1 : 0; + + if(GetPVP()) + this->Message_StringID(MT_Shout,PVP_ON); + else + Message(13, "You no longer follow the ways of discord."); + + SendAppearancePacket(AT_PVP, GetPVP()); + Save(); +} + +void Client::WorldKick() { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_GMKick, sizeof(GMKick_Struct)); + GMKick_Struct* gmk = (GMKick_Struct *)outapp->pBuffer; + strcpy(gmk->name,GetName()); + QueuePacket(outapp); + safe_delete(outapp); + Kick(); +} + +void Client::GMKill() { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_GMKill, sizeof(GMKill_Struct)); + GMKill_Struct* gmk = (GMKill_Struct *)outapp->pBuffer; + strcpy(gmk->name,GetName()); + QueuePacket(outapp); + safe_delete(outapp); +} + +bool Client::CheckAccess(int16 iDBLevel, int16 iDefaultLevel) { + if ((admin >= iDBLevel) || (iDBLevel == 255 && admin >= iDefaultLevel)) + return true; + else + return false; +} + +void Client::MemorizeSpell(uint32 slot,uint32 spellid,uint32 scribing){ + EQApplicationPacket* outapp = new EQApplicationPacket(OP_MemorizeSpell,sizeof(MemorizeSpell_Struct)); + MemorizeSpell_Struct* mss=(MemorizeSpell_Struct*)outapp->pBuffer; + mss->scribing=scribing; + mss->slot=slot; + mss->spell_id=spellid; + outapp->priority = 5; + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::SetFeigned(bool in_feigned) { + if (in_feigned) + { + if(RuleB(Character, FeignKillsPet)) + { + SetPet(0); + } + SetHorseId(0); + entity_list.ClearFeignAggro(this); + forget_timer.Start(FeignMemoryDuration); + } else { + forget_timer.Disable(); + } + feigned=in_feigned; + } + +void Client::LogMerchant(Client* player, Mob* merchant, uint32 quantity, uint32 price, const Item_Struct* item, bool buying) +{ + if(!player || !merchant || !item) + return; + + std::string LogText = "Qty: "; + + char Buffer[255]; + memset(Buffer, 0, sizeof(Buffer)); + + snprintf(Buffer, sizeof(Buffer)-1, "%3i", quantity); + LogText += Buffer; + snprintf(Buffer, sizeof(Buffer)-1, "%10i", price); + LogText += " TotalValue: "; + LogText += Buffer; + snprintf(Buffer, sizeof(Buffer)-1, " ItemID: %7i", item->ID); + LogText += Buffer; + LogText += " "; + snprintf(Buffer, sizeof(Buffer)-1, " %s", item->Name); + LogText += Buffer; + + if (buying==true) { + database.logevents(player->AccountName(),player->AccountID(),player->admin,player->GetName(),merchant->GetName(),"Buying from Merchant",LogText.c_str(),2); + } + else { + database.logevents(player->AccountName(),player->AccountID(),player->admin,player->GetName(),merchant->GetName(),"Selling to Merchant",LogText.c_str(),3); + } +} + +void Client::LogLoot(Client* player, Corpse* corpse, const Item_Struct* item){ + char* logtext; + char itemid[100]; + char itemname[100]; + char coinloot[100]; + if (item!=0){ + memset(itemid,0,sizeof(itemid)); + memset(itemname,0,sizeof(itemid)); + itoa(item->ID,itemid,10); + sprintf(itemname,"%s",item->Name); + logtext=itemname; + + strcat(logtext,"("); + strcat(logtext,itemid); + strcat(logtext,") Looted"); + database.logevents(player->AccountName(),player->AccountID(),player->admin,player->GetName(),corpse->orgname,"Looting Item",logtext,4); + } + else{ + if ((corpse->GetPlatinum() + corpse->GetGold() + corpse->GetSilver() + corpse->GetCopper())>0) { + memset(coinloot,0,sizeof(coinloot)); + sprintf(coinloot,"%i PP %i GP %i SP %i CP",corpse->GetPlatinum(),corpse->GetGold(),corpse->GetSilver(),corpse->GetCopper()); + logtext=coinloot; + strcat(logtext," Looted"); + if (corpse->GetPlatinum()>10000) + database.logevents(player->AccountName(),player->AccountID(),player->admin,player->GetName(),corpse->orgname,"Excessive Loot!",logtext,9); + else + database.logevents(player->AccountName(),player->AccountID(),player->admin,player->GetName(),corpse->orgname,"Looting Money",logtext,5); + } + } +} + + +bool Client::BindWound(Mob* bindmob, bool start, bool fail){ + EQApplicationPacket* outapp = 0; + if(!fail) { + outapp = new EQApplicationPacket(OP_Bind_Wound, sizeof(BindWound_Struct)); + BindWound_Struct* bind_out = (BindWound_Struct*) outapp->pBuffer; + // Start bind + if(!bindwound_timer.Enabled()) { + //make sure we actually have a bandage... and consume it. + int16 bslot = m_inv.HasItemByUse(ItemTypeBandage, 1, invWhereWorn|invWherePersonal); + if (bslot == INVALID_INDEX) { + bind_out->type = 3; + QueuePacket(outapp); + bind_out->type = 7; //this is the wrong message, dont know the right one. + QueuePacket(outapp); + return(true); + } + DeleteItemInInventory(bslot, 1, true); //do we need client update? + + // start complete timer + bindwound_timer.Start(10000); + bindwound_target = bindmob; + + // Send client unlock + bind_out->type = 3; + QueuePacket(outapp); + bind_out->type = 0; + // Client Unlocked + if(!bindmob) { + // send "bindmob dead" to client + bind_out->type = 4; + QueuePacket(outapp); + bind_out->type = 0; + bindwound_timer.Disable(); + bindwound_target = 0; + } + else { + // send bindmob "stand still" + if(!bindmob->IsAIControlled() && bindmob != this ) { + bind_out->type = 2; // ? + //bind_out->type = 3; // ? + bind_out->to = GetID(); // ? + bindmob->CastToClient()->QueuePacket(outapp); + bind_out->type = 0; + bind_out->to = 0; + } + else if (bindmob->IsAIControlled() && bindmob != this ){ + ; // Tell IPC to stand still? + } + else { + ; // Binding self + } + } + } else { + // finish bind + // disable complete timer + bindwound_timer.Disable(); + bindwound_target = 0; + if(!bindmob){ + // send "bindmob gone" to client + bind_out->type = 5; // not in zone + QueuePacket(outapp); + bind_out->type = 0; + } + + else { + if (!GetFeigned() && (bindmob->DistNoRoot(*this) <= 400)) { + // send bindmob bind done + if(!bindmob->IsAIControlled() && bindmob != this ) { + + } + else if(bindmob->IsAIControlled() && bindmob != this ) { + // Tell IPC to resume?? + } + else { + // Binding self + } + // Send client bind done + + bind_out->type = 1; // Done + QueuePacket(outapp); + bind_out->type = 0; + CheckIncreaseSkill(SkillBindWound, nullptr, 5); + + int maxHPBonus = spellbonuses.MaxBindWound + itembonuses.MaxBindWound + aabonuses.MaxBindWound; + + int max_percent = 50 + 10 * maxHPBonus; + + if(GetClass() == MONK && GetSkill(SkillBindWound) > 200) { + max_percent = 70 + 10 * maxHPBonus; + } + + max_percent = mod_bindwound_percent(max_percent, bindmob); + + int max_hp = bindmob->GetMaxHP()*max_percent/100; + + // send bindmob new hp's + if (bindmob->GetHP() < bindmob->GetMaxHP() && bindmob->GetHP() <= (max_hp)-1){ + // 0.120 per skill point, 0.60 per skill level, minimum 3 max 30 + int bindhps = 3; + + + if (GetSkill(SkillBindWound) > 200) { + bindhps += GetSkill(SkillBindWound)*4/10; + } else if (GetSkill(SkillBindWound) >= 10) { + bindhps += GetSkill(SkillBindWound)/4; + } + + //Implementation of aaMithanielsBinding is a guess (the multiplier) + int bindBonus = spellbonuses.BindWound + itembonuses.BindWound + aabonuses.BindWound; + + bindhps += bindhps*bindBonus / 100; + + bindhps = mod_bindwound_hp(bindhps, bindmob); + + //if the bind takes them above the max bindable + //cap it at that value. Dont know if live does it this way + //but it makes sense to me. + int chp = bindmob->GetHP() + bindhps; + if(chp > max_hp) + chp = max_hp; + + bindmob->SetHP(chp); + bindmob->SendHPUpdate(); + } + else { + //I dont have the real, live + Message(15, "You cannot bind wounds above %d%% hitpoints.", max_percent); + if(bindmob->IsClient()) + bindmob->CastToClient()->Message(15, "You cannot have your wounds bound above %d%% hitpoints.", max_percent); + // Too many hp message goes here. + } + } + else { + // Send client bind failed + if(bindmob != this) + bind_out->type = 6; // They moved + else + bind_out->type = 7; // Bandager moved + + QueuePacket(outapp); + bind_out->type = 0; + } + } + } + } + else if (bindwound_timer.Enabled()) { + // You moved + outapp = new EQApplicationPacket(OP_Bind_Wound, sizeof(BindWound_Struct)); + BindWound_Struct* bind_out = (BindWound_Struct*) outapp->pBuffer; + bindwound_timer.Disable(); + bindwound_target = 0; + bind_out->type = 7; + QueuePacket(outapp); + bind_out->type = 3; + QueuePacket(outapp); + } + safe_delete(outapp); + return true; +} + +void Client::SetMaterial(int16 in_slot, uint32 item_id) { + const Item_Struct* item = database.GetItem(item_id); + if (item && (item->ItemClass==ItemClassCommon)) { + if (in_slot==MainHead) + m_pp.item_material[MaterialHead] = item->Material; + else if (in_slot==MainChest) + m_pp.item_material[MaterialChest] = item->Material; + else if (in_slot==MainArms) + m_pp.item_material[MaterialArms] = item->Material; + else if (in_slot==MainWrist1) + m_pp.item_material[MaterialWrist] = item->Material; + /* + // non-live behavior + else if (in_slot==SLOT_BRACER02) + m_pp.item_material[MaterialWrist] = item->Material; + */ + else if (in_slot==MainHands) + m_pp.item_material[MaterialHands] = item->Material; + else if (in_slot==MainLegs) + m_pp.item_material[MaterialLegs] = item->Material; + else if (in_slot==MainFeet) + m_pp.item_material[MaterialFeet] = item->Material; + else if (in_slot==MainPrimary) + m_pp.item_material[MaterialPrimary] = atoi(item->IDFile+2); + else if (in_slot==MainSecondary) + m_pp.item_material[MaterialSecondary] = atoi(item->IDFile+2); + } +} + +void Client::ServerFilter(SetServerFilter_Struct* filter){ + +/* this code helps figure out the filter IDs in the packet if needed + static SetServerFilter_Struct ssss; + int r; + uint32 *o = (uint32 *) &ssss; + uint32 *n = (uint32 *) filter; + for(r = 0; r < (sizeof(SetServerFilter_Struct)/4); r++) { + if(*o != *n) + LogFile->write(EQEMuLog::Debug, "Filter %d changed from %d to %d", r, *o, *n); + o++; n++; + } + memcpy(&ssss, filter, sizeof(SetServerFilter_Struct)); +*/ +#define Filter0(type) \ + if(filter->filters[type] == 1) \ + ClientFilters[type] = FilterShow; \ + else \ + ClientFilters[type] = FilterHide; +#define Filter1(type) \ + if(filter->filters[type] == 0) \ + ClientFilters[type] = FilterShow; \ + else \ + ClientFilters[type] = FilterHide; + + Filter0(FilterGuildChat); + Filter0(FilterSocials); + Filter0(FilterGroupChat); + Filter0(FilterShouts); + Filter0(FilterAuctions); + Filter0(FilterOOC); + Filter0(FilterBadWords); + + if(filter->filters[FilterPCSpells] == 0) + ClientFilters[FilterPCSpells] = FilterShow; + else if(filter->filters[FilterPCSpells] == 1) + ClientFilters[FilterPCSpells] = FilterHide; + else + ClientFilters[FilterPCSpells] = FilterShowGroupOnly; + + Filter1(FilterNPCSpells); + + if(filter->filters[FilterBardSongs] == 0) + ClientFilters[FilterBardSongs] = FilterShow; + else if(filter->filters[FilterBardSongs] == 1) + ClientFilters[FilterBardSongs] = FilterShowSelfOnly; + else if(filter->filters[FilterBardSongs] == 2) + ClientFilters[FilterBardSongs] = FilterShowGroupOnly; + else + ClientFilters[FilterBardSongs] = FilterHide; + + if(filter->filters[FilterSpellCrits] == 0) + ClientFilters[FilterSpellCrits] = FilterShow; + else if(filter->filters[FilterSpellCrits] == 1) + ClientFilters[FilterSpellCrits] = FilterShowSelfOnly; + else + ClientFilters[FilterSpellCrits] = FilterHide; + + if (filter->filters[FilterMeleeCrits] == 0) + ClientFilters[FilterMeleeCrits] = FilterShow; + else if (filter->filters[FilterMeleeCrits] == 1) + ClientFilters[FilterMeleeCrits] = FilterShowSelfOnly; + else + ClientFilters[FilterMeleeCrits] = FilterHide; + + if(filter->filters[FilterSpellDamage] == 0) + ClientFilters[FilterSpellDamage] = FilterShow; + else if(filter->filters[FilterSpellDamage] == 1) + ClientFilters[FilterSpellDamage] = FilterShowSelfOnly; + else + ClientFilters[FilterSpellDamage] = FilterHide; + + Filter0(FilterMyMisses); + Filter0(FilterOthersMiss); + Filter0(FilterOthersHit); + Filter0(FilterMissedMe); + Filter1(FilterDamageShields); + + if (GetClientVersionBit() & BIT_SoDAndLater) { + if (filter->filters[FilterDOT] == 0) + ClientFilters[FilterDOT] = FilterShow; + else if (filter->filters[FilterDOT] == 1) + ClientFilters[FilterDOT] = FilterShowSelfOnly; + else if (filter->filters[FilterDOT] == 2) + ClientFilters[FilterDOT] = FilterShowGroupOnly; + else + ClientFilters[FilterDOT] = FilterHide; + } else { + if (filter->filters[FilterDOT] == 0) // show functions as self only + ClientFilters[FilterDOT] = FilterShowSelfOnly; + else + ClientFilters[FilterDOT] = FilterHide; + } + + Filter1(FilterPetHits); + Filter1(FilterPetMisses); + Filter1(FilterFocusEffects); + Filter1(FilterPetSpells); + + if (GetClientVersionBit() & BIT_SoDAndLater) { + if (filter->filters[FilterHealOverTime] == 0) + ClientFilters[FilterHealOverTime] = FilterShow; + // This is called 'Show Mine Only' in the clients, but functions the same as show + // so instead of apply special logic, just set to show + else if (filter->filters[FilterHealOverTime] == 1) + ClientFilters[FilterHealOverTime] = FilterShow; + else + ClientFilters[FilterHealOverTime] = FilterHide; + } else { + // these clients don't have a 'self only' filter + Filter1(FilterHealOverTime); + } +} + +// this version is for messages with no parameters +void Client::Message_StringID(uint32 type, uint32 string_id, uint32 distance) +{ + if (GetFilter(FilterSpellDamage) == FilterHide && type == MT_NonMelee) + return; + if (GetFilter(FilterMeleeCrits) == FilterHide && type == MT_CritMelee) //98 is self... + return; + if (GetFilter(FilterSpellCrits) == FilterHide && type == MT_SpellCrits) + return; + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SimpleMessage,12); + SimpleMessage_Struct* sms = (SimpleMessage_Struct*)outapp->pBuffer; + sms->color=type; + sms->string_id=string_id; + + sms->unknown8=0; + + if(distance>0) + entity_list.QueueCloseClients(this,outapp,false,distance); + else + QueuePacket(outapp); + safe_delete(outapp); +} + +// +// this list of 9 args isn't how I want to do it, but to use va_arg +// you have to know how many args you're expecting, and to do that we have +// to load the eqstr file and count them in the string. +// This hack sucks but it's gonna work for now. +// +void Client::Message_StringID(uint32 type, uint32 string_id, const char* message1, + const char* message2,const char* message3,const char* message4, + const char* message5,const char* message6,const char* message7, + const char* message8,const char* message9, uint32 distance) +{ + if (GetFilter(FilterSpellDamage) == FilterHide && type == MT_NonMelee) + return; + if (GetFilter(FilterMeleeCrits) == FilterHide && type == MT_CritMelee) //98 is self... + return; + if (GetFilter(FilterSpellCrits) == FilterHide && type == MT_SpellCrits) + return; + if (GetFilter(FilterDamageShields) == FilterHide && type == MT_DS) + return; + + int i, argcount, length; + char *bufptr; + const char *message_arg[9] = {0}; + + if(type==MT_Emote) + type=4; + + if(!message1) + { + Message_StringID(type, string_id); // use the simple message instead + return; + } + + i = 0; + message_arg[i++] = message1; + message_arg[i++] = message2; + message_arg[i++] = message3; + message_arg[i++] = message4; + message_arg[i++] = message5; + message_arg[i++] = message6; + message_arg[i++] = message7; + message_arg[i++] = message8; + message_arg[i++] = message9; + + for(argcount = length = 0; message_arg[argcount]; argcount++) + length += strlen(message_arg[argcount]) + 1; + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_FormattedMessage, length+13); + FormattedMessage_Struct *fm = (FormattedMessage_Struct *)outapp->pBuffer; + fm->string_id = string_id; + fm->type = type; + bufptr = fm->message; + for(i = 0; i < argcount; i++) + { + strcpy(bufptr, message_arg[i]); + bufptr += strlen(message_arg[i]) + 1; + } + + + if(distance>0) + entity_list.QueueCloseClients(this,outapp,false,distance); + else + QueuePacket(outapp); + safe_delete(outapp); +} + +// helper function, returns true if we should see the message +bool Client::FilteredMessageCheck(Mob *sender, eqFilterType filter) +{ + eqFilterMode mode = GetFilter(filter); + // easy ones first + if (mode == FilterShow) + return true; + else if (mode == FilterHide) + return false; + + if (!sender && mode == FilterHide) { + return false; + } else if (sender) { + if (this == sender) { + if (mode == FilterHide) // don't need to check others + return false; + } else if (mode == FilterShowSelfOnly) { // we know sender isn't us + return false; + } else if (mode == FilterShowGroupOnly) { + Group *g = GetGroup(); + Raid *r = GetRaid(); + if (g) { + if (g->IsGroupMember(sender)) + return true; + } else if (r && sender->IsClient()) { + uint32 rgid1 = r->GetGroup(this); + uint32 rgid2 = r->GetGroup(sender->CastToClient()); + if (rgid1 != 0xFFFFFFFF && rgid1 == rgid2) + return true; + } else { + return false; + } + } + } + + // we passed our checks + return true; +} + +void Client::FilteredMessage_StringID(Mob *sender, uint32 type, + eqFilterType filter, uint32 string_id) +{ + if (!FilteredMessageCheck(sender, filter)) + return; + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_SimpleMessage, 12); + SimpleMessage_Struct *sms = (SimpleMessage_Struct *)outapp->pBuffer; + sms->color = type; + sms->string_id = string_id; + + sms->unknown8 = 0; + + QueuePacket(outapp); + safe_delete(outapp); + + return; +} + +void Client::FilteredMessage_StringID(Mob *sender, uint32 type, eqFilterType filter, uint32 string_id, + const char *message1, const char *message2, const char *message3, + const char *message4, const char *message5, const char *message6, + const char *message7, const char *message8, const char *message9) +{ + if (!FilteredMessageCheck(sender, filter)) + return; + + int i, argcount, length; + char *bufptr; + const char *message_arg[9] = {0}; + + if (type == MT_Emote) + type = 4; + + if (!message1) { + FilteredMessage_StringID(sender, type, filter, string_id); // use the simple message instead + return; + } + + i = 0; + message_arg[i++] = message1; + message_arg[i++] = message2; + message_arg[i++] = message3; + message_arg[i++] = message4; + message_arg[i++] = message5; + message_arg[i++] = message6; + message_arg[i++] = message7; + message_arg[i++] = message8; + message_arg[i++] = message9; + + for (argcount = length = 0; message_arg[argcount]; argcount++) + length += strlen(message_arg[argcount]) + 1; + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_FormattedMessage, length+13); + FormattedMessage_Struct *fm = (FormattedMessage_Struct *)outapp->pBuffer; + fm->string_id = string_id; + fm->type = type; + bufptr = fm->message; + for (i = 0; i < argcount; i++) { + strcpy(bufptr, message_arg[i]); + bufptr += strlen(message_arg[i]) + 1; + } + + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::SetTint(int16 in_slot, uint32 color) { + Color_Struct new_color; + new_color.color = color; + SetTint(in_slot, new_color); +} + +// Still need to reconcile bracer01 versus bracer02 +void Client::SetTint(int16 in_slot, Color_Struct& color) { + if (in_slot==MainHead) + m_pp.item_tint[MaterialHead].color=color.color; + else if (in_slot==MainArms) + m_pp.item_tint[MaterialArms].color=color.color; + else if (in_slot==MainWrist1) + m_pp.item_tint[MaterialWrist].color=color.color; + /* + // non-live behavior + else if (in_slot==SLOT_BRACER02) + m_pp.item_tint[MaterialWrist].color=color.color; + */ + else if (in_slot==MainHands) + m_pp.item_tint[MaterialHands].color=color.color; + else if (in_slot==MainPrimary) + m_pp.item_tint[MaterialPrimary].color=color.color; + else if (in_slot==MainSecondary) + m_pp.item_tint[MaterialSecondary].color=color.color; + else if (in_slot==MainChest) + m_pp.item_tint[MaterialChest].color=color.color; + else if (in_slot==MainLegs) + m_pp.item_tint[MaterialLegs].color=color.color; + else if (in_slot==MainFeet) + m_pp.item_tint[MaterialFeet].color=color.color; +} + +void Client::SetHideMe(bool flag) +{ + EQApplicationPacket app; + + gmhideme = flag; + + if(gmhideme) + { + database.SetHideMe(AccountID(),true); + CreateDespawnPacket(&app, false); + entity_list.RemoveFromTargets(this); + trackable = false; + } + else + { + database.SetHideMe(AccountID(),false); + CreateSpawnPacket(&app); + trackable = true; + } + + entity_list.QueueClientsStatus(this, &app, true, 0, Admin()-1); +} + +void Client::SetLanguageSkill(int langid, int value) +{ + if (langid >= MAX_PP_LANGUAGE) + return; //Invalid Language + + if (value > 100) + value = 100; //Max lang value + + m_pp.languages[langid] = value; + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SkillUpdate, sizeof(SkillUpdate_Struct)); + SkillUpdate_Struct* skill = (SkillUpdate_Struct*)outapp->pBuffer; + skill->skillId = 100 + langid; + skill->value = m_pp.languages[langid]; + QueuePacket(outapp); + safe_delete(outapp); + + Message_StringID( MT_Skills, LANG_SKILL_IMPROVED ); //Notify the client +} + +void Client::LinkDead() +{ + if (GetGroup()) + { + entity_list.MessageGroup(this,true,15,"%s has gone linkdead.",GetName()); + GetGroup()->DelMember(this); + } + Raid *raid = entity_list.GetRaidByClient(this); + if(raid){ + raid->MemberZoned(this); + } +// save_timer.Start(2500); + linkdead_timer.Start(RuleI(Zone,ClientLinkdeadMS)); + SendAppearancePacket(AT_Linkdead, 1); + client_state = CLIENT_LINKDEAD; + AI_Start(CLIENT_LD_TIMEOUT); +} + +uint8 Client::SlotConvert(uint8 slot,bool bracer){ + uint8 slot2=0; // why are we returning MainCharm instead of INVALID_INDEX? (must be a pre-charm segment...) + if(bracer) + return MainWrist2; + switch(slot){ + case MaterialHead: + slot2=MainHead; + break; + case MaterialChest: + slot2=MainChest; + break; + case MaterialArms: + slot2=MainArms; + break; + case MaterialWrist: + slot2=MainWrist1; + break; + case MaterialHands: + slot2=MainHands; + break; + case MaterialLegs: + slot2=MainLegs; + break; + case MaterialFeet: + slot2=MainFeet; + break; + } + return slot2; +} + +uint8 Client::SlotConvert2(uint8 slot){ + uint8 slot2=0; // same as above... + switch(slot){ + case MainHead: + slot2=MaterialHead; + break; + case MainChest: + slot2=MaterialChest; + break; + case MainArms: + slot2=MaterialArms; + break; + case MainWrist1: + slot2=MaterialWrist; + break; + case MainHands: + slot2=MaterialHands; + break; + case MainLegs: + slot2=MaterialLegs; + break; + case MainFeet: + slot2=MaterialFeet; + break; + } + return slot2; +} + +void Client::Escape() +{ + entity_list.RemoveFromTargets(this, true); + SetInvisible(1); + + Message_StringID(MT_Skills, ESCAPE); +} + +float Client::CalcPriceMod(Mob* other, bool reverse) +{ + float chaformula = 0; + if (other) + { + int factionlvl = GetFactionLevel(CharacterID(), other->CastToNPC()->GetNPCTypeID(), GetRace(), GetClass(), GetDeity(), other->CastToNPC()->GetPrimaryFaction(), other); + if (factionlvl >= FACTION_APPREHENSIVE) // Apprehensive or worse. + { + if (GetCHA() > 103) + { + chaformula = (GetCHA() - 103)*((-(RuleR(Merchant, ChaBonusMod))/100)*(RuleI(Merchant, PriceBonusPct))); // This will max out price bonus. + if (chaformula < -1*(RuleI(Merchant, PriceBonusPct))) + chaformula = -1*(RuleI(Merchant, PriceBonusPct)); + } + else if (GetCHA() < 103) + { + chaformula = (103 - GetCHA())*(((RuleR(Merchant, ChaPenaltyMod))/100)*(RuleI(Merchant, PricePenaltyPct))); // This will bottom out price penalty. + if (chaformula > 1*(RuleI(Merchant, PricePenaltyPct))) + chaformula = 1*(RuleI(Merchant, PricePenaltyPct)); + } + } + if (factionlvl <= FACTION_INDIFFERENT) // Indifferent or better. + { + if (GetCHA() > 75) + { + chaformula = (GetCHA() - 75)*((-(RuleR(Merchant, ChaBonusMod))/100)*(RuleI(Merchant, PriceBonusPct))); // This will max out price bonus. + if (chaformula < -1*(RuleI(Merchant, PriceBonusPct))) + chaformula = -1*(RuleI(Merchant, PriceBonusPct)); + } + else if (GetCHA() < 75) + { + chaformula = (75 - GetCHA())*(((RuleR(Merchant, ChaPenaltyMod))/100)*(RuleI(Merchant, PricePenaltyPct))); // Faction modifier keeps up from reaching bottom price penalty. + if (chaformula > 1*(RuleI(Merchant, PricePenaltyPct))) + chaformula = 1*(RuleI(Merchant, PricePenaltyPct)); + } + } + } + + if (reverse) + chaformula *= -1; //For selling + //Now we have, for example, 10 + chaformula /= 100; //Convert to 0.10 + chaformula += 1; //Convert to 1.10; + return chaformula; //Returns 1.10, expensive stuff! +} + +//neat idea from winter's roar, not implemented +void Client::Insight(uint32 t_id) +{ + Mob* who = entity_list.GetMob(t_id); + if (!who) + return; + if (!who->IsNPC()) + { + Message(0,"This ability can only be used on NPCs."); + return; + } + if (Dist(*who) > 200) + { + Message(0,"You must get closer to your target!"); + return; + } + if (!CheckLosFN(who)) + { + Message(0,"You must be able to see your target!"); + return; + } + char hitpoints[64]; + char resists[320]; + char dmg[64]; + memset(hitpoints,0,sizeof(hitpoints)); + memset(resists,0,sizeof(resists)); + memset(dmg,0,sizeof(dmg)); + //Start with HP blah + int avg_hp = GetLevelHP(who->GetLevel()); + int cur_hp = who->GetHP(); + if (cur_hp == avg_hp) + { + strn0cpy(hitpoints,"averagely tough",32); + } + else if (cur_hp >= avg_hp*5) + { + strn0cpy(hitpoints,"extremely tough",32); + } + else if (cur_hp >= avg_hp*4) + { + strn0cpy(hitpoints,"exceptionally tough",32); + } + else if (cur_hp >= avg_hp*3) + { + strn0cpy(hitpoints,"very tough",32); + } + else if (cur_hp >= avg_hp*2) + { + strn0cpy(hitpoints,"quite tough",32); + } + else if (cur_hp >= avg_hp*1.25) + { + strn0cpy(hitpoints,"rather tough",32); + } + else if (cur_hp > avg_hp) + { + strn0cpy(hitpoints,"slightly tough",32); + } + else if (cur_hp <= avg_hp*0.20) + { + strn0cpy(hitpoints,"extremely frail",32); + } + else if (cur_hp <= avg_hp*0.25) + { + strn0cpy(hitpoints,"exceptionally frail",32); + } + else if (cur_hp <= avg_hp*0.33) + { + strn0cpy(hitpoints,"very frail",32); + } + else if (cur_hp <= avg_hp*0.50) + { + strn0cpy(hitpoints,"quite frail",32); + } + else if (cur_hp <= avg_hp*0.75) + { + strn0cpy(hitpoints,"rather frail",32); + } + else if (cur_hp < avg_hp) + { + strn0cpy(hitpoints,"slightly frail",32); + } + + int avg_dmg = who->CastToNPC()->GetMaxDamage(who->GetLevel()); + int cur_dmg = who->CastToNPC()->GetMaxDMG(); + if (cur_dmg == avg_dmg) + { + strn0cpy(dmg,"averagely strong",32); + } + else if (cur_dmg >= avg_dmg*4) + { + strn0cpy(dmg,"extremely strong",32); + } + else if (cur_dmg >= avg_dmg*3) + { + strn0cpy(dmg,"exceptionally strong",32); + } + else if (cur_dmg >= avg_dmg*2) + { + strn0cpy(dmg,"very strong",32); + } + else if (cur_dmg >= avg_dmg*1.25) + { + strn0cpy(dmg,"quite strong",32); + } + else if (cur_dmg >= avg_dmg*1.10) + { + strn0cpy(dmg,"rather strong",32); + } + else if (cur_dmg > avg_dmg) + { + strn0cpy(dmg,"slightly strong",32); + } + else if (cur_dmg <= avg_dmg*0.20) + { + strn0cpy(dmg,"extremely weak",32); + } + else if (cur_dmg <= avg_dmg*0.25) + { + strn0cpy(dmg,"exceptionally weak",32); + } + else if (cur_dmg <= avg_dmg*0.33) + { + strn0cpy(dmg,"very weak",32); + } + else if (cur_dmg <= avg_dmg*0.50) + { + strn0cpy(dmg,"quite weak",32); + } + else if (cur_dmg <= avg_dmg*0.75) + { + strn0cpy(dmg,"rather weak",32); + } + else if (cur_dmg < avg_dmg) + { + strn0cpy(dmg,"slightly weak",32); + } + + //Resists + int res; + int i = 1; + + //MR + res = who->GetResist(i); + i++; + if (res >= 1000) + { + strcat(resists,"immune"); + } + else if (res >= 500) + { + strcat(resists,"practically immune"); + } + else if (res >= 250) + { + strcat(resists,"exceptionally resistant"); + } + else if (res >= 150) + { + strcat(resists,"very resistant"); + } + else if (res >= 100) + { + strcat(resists,"fairly resistant"); + } + else if (res >= 50) + { + strcat(resists,"averagely resistant"); + } + else if (res >= 25) + { + strcat(resists,"weakly resistant"); + } + else + { + strcat(resists,"barely resistant"); + } + strcat(resists," to magic, "); + + //FR + res = who->GetResist(i); + i++; + if (res >= 1000) + { + strcat(resists,"immune"); + } + else if (res >= 500) + { + strcat(resists,"practically immune"); + } + else if (res >= 250) + { + strcat(resists,"exceptionally resistant"); + } + else if (res >= 150) + { + strcat(resists,"very resistant"); + } + else if (res >= 100) + { + strcat(resists,"fairly resistant"); + } + else if (res >= 50) + { + strcat(resists,"averagely resistant"); + } + else if (res >= 25) + { + strcat(resists,"weakly resistant"); + } + else + { + strcat(resists,"barely resistant"); + } + strcat(resists," to fire, "); + + //CR + res = who->GetResist(i); + i++; + if (res >= 1000) + { + strcat(resists,"immune"); + } + else if (res >= 500) + { + strcat(resists,"practically immune"); + } + else if (res >= 250) + { + strcat(resists,"exceptionally resistant"); + } + else if (res >= 150) + { + strcat(resists,"very resistant"); + } + else if (res >= 100) + { + strcat(resists,"fairly resistant"); + } + else if (res >= 50) + { + strcat(resists,"averagely resistant"); + } + else if (res >= 25) + { + strcat(resists,"weakly resistant"); + } + else + { + strcat(resists,"barely resistant"); + } + strcat(resists," to cold, "); + + //PR + res = who->GetResist(i); + i++; + if (res >= 1000) + { + strcat(resists,"immune"); + } + else if (res >= 500) + { + strcat(resists,"practically immune"); + } + else if (res >= 250) + { + strcat(resists,"exceptionally resistant"); + } + else if (res >= 150) + { + strcat(resists,"very resistant"); + } + else if (res >= 100) + { + strcat(resists,"fairly resistant"); + } + else if (res >= 50) + { + strcat(resists,"averagely resistant"); + } + else if (res >= 25) + { + strcat(resists,"weakly resistant"); + } + else + { + strcat(resists,"barely resistant"); + } + strcat(resists," to poison, and "); + + //MR + res = who->GetResist(i); + i++; + if (res >= 1000) + { + strcat(resists,"immune"); + } + else if (res >= 500) + { + strcat(resists,"practically immune"); + } + else if (res >= 250) + { + strcat(resists,"exceptionally resistant"); + } + else if (res >= 150) + { + strcat(resists,"very resistant"); + } + else if (res >= 100) + { + strcat(resists,"fairly resistant"); + } + else if (res >= 50) + { + strcat(resists,"averagely resistant"); + } + else if (res >= 25) + { + strcat(resists,"weakly resistant"); + } + else + { + strcat(resists,"barely resistant"); + } + strcat(resists," to disease."); + + Message(0,"Your target is a level %i %s. It appears %s and %s for its level. It seems %s",who->GetLevel(),GetEQClassName(who->GetClass(),1),dmg,hitpoints,resists); +} + +void Client::ChangeSQLLog(const char *file) { + if(SQL_log != nullptr) { + fclose(SQL_log); + SQL_log = nullptr; + } + if(file != nullptr) { + if(strstr(file, "..") != nullptr) { + Message(13, ".. is forbibben in SQL log file names."); + return; + } + char buf[512]; + snprintf(buf, 511, "%s%s", SQL_LOG_PATH, file); + buf[511] = '\0'; + SQL_log = fopen(buf, "a"); + if(SQL_log == nullptr) { + Message(13, "Unable to open SQL log file: %s\n", strerror(errno)); + } + } +} + +void Client::LogSQL(const char *fmt, ...) { + if(SQL_log == nullptr) + return; + + va_list argptr; + va_start(argptr, fmt); + vfprintf(SQL_log, fmt, argptr ); + fputc('\n', SQL_log); + va_end(argptr); +} + +void Client::GetGroupAAs(GroupLeadershipAA_Struct *into) const { + memcpy(into, &m_pp.leader_abilities, sizeof(GroupLeadershipAA_Struct)); +} + +void Client::EnteringMessages(Client* client) +{ + //server rules + char *rules; + rules = new char [4096]; + + if(database.GetVariable("Rules", rules, 4096)) + { + uint8 flag = database.GetAgreementFlag(client->AccountID()); + if(!flag) + { + client->Message(13,"You must agree to the Rules, before you can move. (type #serverrules to view the rules)"); + client->Message(13,"You must agree to the Rules, before you can move. (type #serverrules to view the rules)"); + client->Message(13,"You must agree to the Rules, before you can move. (type #serverrules to view the rules)"); + client->SendAppearancePacket(AT_Anim, ANIM_FREEZE); + } + } + safe_delete_array(rules); +} + +void Client::SendRules(Client* client) +{ + char *rules; + rules = new char [4096]; + char *ptr; + + database.GetVariable("Rules", rules, 4096); + + ptr = strtok(rules, "\n"); + while(ptr != nullptr) + { + + client->Message(0,"%s",ptr); + ptr = strtok(nullptr, "\n"); + } + safe_delete_array(rules); +} + +void Client::SetEndurance(int32 newEnd) +{ + /*Endurance can't be less than 0 or greater than max*/ + if(newEnd < 0) + newEnd = 0; + else if(newEnd > GetMaxEndurance()){ + newEnd = GetMaxEndurance(); + } + + cur_end = newEnd; + SendManaUpdatePacket(); +} + +void Client::SacrificeConfirm(Client *caster) { + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_Sacrifice, sizeof(Sacrifice_Struct)); + Sacrifice_Struct *ss = (Sacrifice_Struct*)outapp->pBuffer; + + if(!caster || PendingSacrifice) return; + + if(GetLevel() < RuleI(Spells, SacrificeMinLevel)){ + caster->Message_StringID(13, SAC_TOO_LOW); //This being is not a worthy sacrifice. + return; + } + if (GetLevel() > RuleI(Spells, SacrificeMaxLevel)) { + caster->Message_StringID(13, SAC_TOO_HIGH); + return; + } + + ss->CasterID = caster->GetID(); + ss->TargetID = GetID(); + ss->Confirm = 0; + QueuePacket(outapp); + safe_delete(outapp); + // We store the Caster's name, because when the packet comes back, it only has the victim's entityID in it, + // not the caster. + SacrificeCaster += caster->GetName(); + PendingSacrifice = true; +} + +//Essentially a special case death function +void Client::Sacrifice(Client *caster) +{ + if(GetLevel() >= RuleI(Spells, SacrificeMinLevel) && GetLevel() <= RuleI(Spells, SacrificeMaxLevel)){ + int exploss = (int)(GetLevel() * (GetLevel() / 18.0) * 12000); + if(exploss < GetEXP()){ + SetEXP(GetEXP()-exploss, GetAAXP()); + SendLogoutPackets(); + + //make our become corpse packet, and queue to ourself before OP_Death. + EQApplicationPacket app2(OP_BecomeCorpse, sizeof(BecomeCorpse_Struct)); + BecomeCorpse_Struct* bc = (BecomeCorpse_Struct*)app2.pBuffer; + bc->spawn_id = GetID(); + bc->x = GetX(); + bc->y = GetY(); + bc->z = GetZ(); + QueuePacket(&app2); + + // make death packet + EQApplicationPacket app(OP_Death, sizeof(Death_Struct)); + Death_Struct* d = (Death_Struct*)app.pBuffer; + d->spawn_id = GetID(); + d->killer_id = caster ? caster->GetID() : 0; + d->bindzoneid = GetPP().binds[0].zoneId; + d->spell_id = SPELL_UNKNOWN; + d->attack_skill = 0xe7; + d->damage = 0; + app.priority = 6; + entity_list.QueueClients(this, &app); + + BuffFadeAll(); + UnmemSpellAll(); + Group *g = GetGroup(); + if(g){ + g->MemberZoned(this); + } + Raid *r = entity_list.GetRaidByClient(this); + if(r){ + r->MemberZoned(this); + } + ClearAllProximities(); + if(RuleB(Character, LeaveCorpses)){ + Corpse *new_corpse = new Corpse(this, 0); + entity_list.AddCorpse(new_corpse, GetID()); + SetID(0); + entity_list.QueueClients(this, &app2, true); + } + Save(); + GoToDeath(); + caster->SummonItem(RuleI(Spells, SacrificeItemID)); + } + } + else{ + caster->Message_StringID(13, SAC_TOO_LOW); //This being is not a worthy sacrifice. + } +} + +void Client::SendOPTranslocateConfirm(Mob *Caster, uint16 SpellID) { + + if(!Caster || PendingTranslocate) return; + + const SPDat_Spell_Struct &Spell = spells[SpellID]; + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_Translocate, sizeof(Translocate_Struct)); + Translocate_Struct *ts = (Translocate_Struct*)outapp->pBuffer; + + strcpy(ts->Caster, Caster->GetName()); + ts->SpellID = SpellID; + + if((SpellID == 1422) || (SpellID == 1334) || (SpellID == 3243)) { + ts->ZoneID = m_pp.binds[0].zoneId; + ts->x = m_pp.binds[0].x; + ts->y = m_pp.binds[0].y; + ts->z = m_pp.binds[0].z; + } + else { + ts->ZoneID = database.GetZoneID(Spell.teleport_zone); + ts->y = Spell.base[0]; + ts->x = Spell.base[1]; + ts->z = Spell.base[2]; + } + + ts->unknown008 = 0; + ts->Complete = 0; + + PendingTranslocateData = *ts; + PendingTranslocate=true; + TranslocateTime = time(nullptr); + + QueuePacket(outapp); + safe_delete(outapp); + + return; +} +void Client::SendPickPocketResponse(Mob *from, uint32 amt, int type, const Item_Struct* item){ + EQApplicationPacket* outapp = new EQApplicationPacket(OP_PickPocket, sizeof(sPickPocket_Struct)); + sPickPocket_Struct* pick_out = (sPickPocket_Struct*) outapp->pBuffer; + pick_out->coin = amt; + pick_out->from = GetID(); + pick_out->to = from->GetID(); + pick_out->myskill = GetSkill(SkillPickPockets); + + if((type >= PickPocketPlatinum) && (type <= PickPocketCopper) && (amt == 0)) + type = PickPocketFailed; + + pick_out->type = type; + if(item) + strcpy(pick_out->itemname, item->Name); + else + pick_out->itemname[0] = '\0'; + //if we do not send this packet the client will lock up and require the player to relog. + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::SetHoTT(uint32 mobid) { + EQApplicationPacket *outapp = new EQApplicationPacket(OP_TargetHoTT, sizeof(ClientTarget_Struct)); + ClientTarget_Struct *ct = (ClientTarget_Struct *) outapp->pBuffer; + ct->new_target = mobid; + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::SendPopupToClient(const char *Title, const char *Text, uint32 PopupID, uint32 Buttons, uint32 Duration) { + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_OnLevelMessage, sizeof(OnLevelMessage_Struct)); + OnLevelMessage_Struct *olms = (OnLevelMessage_Struct *) outapp->pBuffer; + + if((strlen(Title) > (sizeof(olms->Title)-1)) || + (strlen(Text) > (sizeof(olms->Text)-1))) return; + + strcpy(olms->Title, Title); + strcpy(olms->Text, Text); + + olms->Buttons = Buttons; + + if(Duration > 0) + olms->Duration = Duration * 1000; + else + olms->Duration = 0xffffffff; + + olms->PopupID = PopupID; + olms->NegativeID = 0; + + sprintf(olms->ButtonName0, "%s", "Yes"); + sprintf(olms->ButtonName1, "%s", "No"); + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::SendWindow(uint32 PopupID, uint32 NegativeID, uint32 Buttons, const char *ButtonName0, const char *ButtonName1, uint32 Duration, int title_type, Client* target, const char *Title, const char *Text, ...) { + va_list argptr; + char buffer[4096]; + + va_start(argptr, Text); + vsnprintf(buffer, sizeof(buffer), Text, argptr); + va_end(argptr); + + size_t len = strlen(buffer); + + EQApplicationPacket* app = new EQApplicationPacket(OP_OnLevelMessage, sizeof(OnLevelMessage_Struct)); + OnLevelMessage_Struct* olms=(OnLevelMessage_Struct*)app->pBuffer; + + if(strlen(Text) > (sizeof(olms->Text)-1)) + return; + + if(!target) + title_type = 0; + + switch (title_type) + { + case 1: { + char name[64] = ""; + strcpy(name, target->GetName()); + if(target->GetLastName()) { + char last_name[64] = ""; + strcpy(last_name, target->GetLastName()); + strcat(name, " "); + strcat(name, last_name); + } + strcpy(olms->Title, name); + break; + } + case 2: { + if(target->GuildID()) { + char *guild_name = (char*)guild_mgr.GetGuildName(target->GuildID()); + strcpy(olms->Title, guild_name); + } + else { + strcpy(olms->Title, "No Guild"); + } + break; + } + default: { + strcpy(olms->Title, Title); + break; + } + } + + memcpy(olms->Text, buffer, len+1); + + olms->Buttons = Buttons; + + sprintf(olms->ButtonName0, "%s", ButtonName0); + sprintf(olms->ButtonName1, "%s", ButtonName1); + + if(Duration > 0) + olms->Duration = Duration * 1000; + else + olms->Duration = 0xffffffff; + + olms->PopupID = PopupID; + olms->NegativeID = NegativeID; + + FastQueuePacket(&app); +} + +void Client::KeyRingLoad() +{ + std::string query = StringFormat("SELECT item_id FROM keyring " + "WHERE char_id = '%i' ORDER BY item_id", character_id); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + std::cerr << "Error in Client::KeyRingLoad query '" << query << "' " << results.ErrorMessage() << std::endl; + return; + } + + for (auto row = results.begin(); row != results.end(); ++row) + keyring.push_back(atoi(row[0])); + +} + +void Client::KeyRingAdd(uint32 item_id) +{ +<<<<<<< HEAD + if(0==item_id) + return; + + bool found = KeyRingCheck(item_id); + if (found) + return; + + std::string query = StringFormat("INSERT INTO keyring(char_id, item_id) VALUES(%i, %i)", character_id, item_id); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + std::cerr << "Error in Doors::HandleClick query '" << query << "' " << results.ErrorMessage() << std::endl; + return; + } + + Message(4,"Added to keyring."); + + keyring.push_back(item_id); +======= + if(0==item_id)return; + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + uint32 affected_rows = 0; + query = new char[256]; + bool bFound = KeyRingCheck(item_id); + if(!bFound){ + sprintf(query, "INSERT INTO keyring(char_id,item_id) VALUES(%i,%i)",character_id,item_id); + if(database.RunQuery(query, strlen(query), errbuf, 0, &affected_rows)) { + Message(4,"Added to keyring."); + + /* QS: PlayerLogKeyringAddition */ + if (RuleB(QueryServ, PlayerLogKeyringAddition)){ + std::string event_desc = StringFormat("itemid:%i in zoneid:%i instid:%i", item_id, this->GetZoneID(), this->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_Keyring_Addition, this->CharacterID(), event_desc); + } + safe_delete_array(query); + } + else { + std::cerr << "Error in Doors::HandleClick query '" << query << "' " << errbuf << std::endl; + safe_delete_array(query); + return; + } + keyring.push_back(item_id); + } +>>>>>>> ca84040a3939b71e835701e2cc52fe199e386382 +} + +bool Client::KeyRingCheck(uint32 item_id) +{ + for(std::list::iterator iter = keyring.begin(); + iter != keyring.end(); + ++iter) + { + if(*iter == item_id) + return true; + } + return false; +} + +void Client::KeyRingList() +{ + Message(4,"Keys on Keyring:"); + const Item_Struct *item = 0; + for(std::list::iterator iter = keyring.begin(); + iter != keyring.end(); + ++iter) + { + if ((item = database.GetItem(*iter))!=nullptr) { + Message(4,item->Name); + } + } +} + +bool Client::IsDiscovered(uint32 itemid) { + + std::string query = StringFormat("SELECT count(*) FROM discovered_items WHERE item_id = '%lu'", itemid); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + std::cerr << "Error in IsDiscovered query '" << query << "' " << results.ErrorMessage() << std::endl; + return false; + } + + auto row = results.begin(); + + return atoi(row[0]) != 0; +} + +void Client::DiscoverItem(uint32 itemid) { + + std::string query = StringFormat("INSERT INTO discovered_items SET " + "item_id = %lu, char_name = '%s', " + "discovered_date = UNIX_TIMESTAMP(), account_status=%i", + itemid, GetName(), Admin()); + auto results = database.QueryDatabase(query); + + parse->EventPlayer(EVENT_DISCOVER_ITEM, this, "", itemid); +} + +void Client::UpdateLFP() { + + Group *g = GetGroup(); + + if(g && !g->IsLeader(this)) { + database.SetLFP(CharacterID(), false); + worldserver.StopLFP(CharacterID()); + LFP = false; + return; + } + + GroupLFPMemberEntry LFPMembers[MAX_GROUP_MEMBERS]; + + for(unsigned int i=0; iGetZoneID(); + + if(g) { + // Fill the LFPMembers array with the rest of the group members, excluding ourself + // We don't fill in the class, level or zone, because we may not be able to determine + // them if the other group members are not in this zone. World will fill in this information + // for us, if it can. + int NextFreeSlot = 1; + for(unsigned int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if((g->membername[i][0] != '\0') && strcasecmp(g->membername[i], LFPMembers[0].Name)) + strcpy(LFPMembers[NextFreeSlot++].Name, g->membername[i]); + } + } + worldserver.UpdateLFP(CharacterID(), LFPMembers); +} + +uint16 Client::GetPrimarySkillValue() +{ + SkillUseTypes skill = HIGHEST_SKILL; //because nullptr == 0, which is 1H Slashing, & we want it to return 0 from GetSkill + bool equiped = m_inv.GetItem(MainPrimary); + + if (!equiped) + skill = SkillHandtoHand; + + else { + + uint8 type = m_inv.GetItem(MainPrimary)->GetItem()->ItemType; //is this the best way to do this? + + switch (type) + { + case ItemType1HSlash: // 1H Slashing + { + skill = Skill1HSlashing; + break; + } + case ItemType2HSlash: // 2H Slashing + { + skill = Skill2HSlashing; + break; + } + case ItemType1HPiercing: // Piercing + { + skill = Skill1HPiercing; + break; + } + case ItemType1HBlunt: // 1H Blunt + { + skill = Skill1HBlunt; + break; + } + case ItemType2HBlunt: // 2H Blunt + { + skill = Skill2HBlunt; + break; + } + case ItemType2HPiercing: // 2H Piercing + { + skill = Skill1HPiercing; // change to Skill2HPiercing once activated + break; + } + case ItemTypeMartial: // Hand to Hand + { + skill = SkillHandtoHand; + break; + } + default: // All other types default to Hand to Hand + { + skill = SkillHandtoHand; + break; + } + } + } + + return GetSkill(skill); +} + +uint16 Client::GetTotalATK() +{ + uint16 AttackRating = 0; + uint16 WornCap = itembonuses.ATK; + + if(IsClient()) { + AttackRating = ((WornCap * 1.342) + (GetSkill(SkillOffense) * 1.345) + ((GetSTR() - 66) * 0.9) + (GetPrimarySkillValue() * 2.69)); + AttackRating += aabonuses.ATK + GroupLeadershipAAOffenseEnhancement(); + + if (AttackRating < 10) + AttackRating = 10; + } + else + AttackRating = GetATK(); + + AttackRating += spellbonuses.ATK; + + return AttackRating; +} + +uint16 Client::GetATKRating() +{ + uint16 AttackRating = 0; + if(IsClient()) { + AttackRating = (GetSkill(SkillOffense) * 1.345) + ((GetSTR() - 66) * 0.9) + (GetPrimarySkillValue() * 2.69); + + if (AttackRating < 10) + AttackRating = 10; + } + return AttackRating; +} + +void Client::VoiceMacroReceived(uint32 Type, char *Target, uint32 MacroNumber) { + + uint32 GroupOrRaidID = 0; + + switch(Type) { + + case VoiceMacroGroup: { + + Group* g = GetGroup(); + + if(g) + GroupOrRaidID = g->GetID(); + else + return; + + break; + } + + case VoiceMacroRaid: { + + Raid* r = GetRaid(); + + if(r) + GroupOrRaidID = r->GetID(); + else + return; + + break; + } + } + + if(!worldserver.SendVoiceMacro(this, Type, Target, MacroNumber, GroupOrRaidID)) + Message(0, "Error: World server disconnected"); +} + +void Client::ClearGroupAAs() { + + for(unsigned int i = 0; i < MAX_GROUP_LEADERSHIP_AA_ARRAY; i++) + m_pp.leader_abilities.ranks[i] = 0; + + m_pp.group_leadership_points = 0; + m_pp.raid_leadership_points = 0; + m_pp.group_leadership_exp = 0; + m_pp.raid_leadership_exp = 0; + + Save(); +} + +void Client::UpdateGroupAAs(int32 points, uint32 type) { + + switch(type) + { + case 0: + { + m_pp.group_leadership_points += points; + break; + } + case 1: + { + m_pp.raid_leadership_points += points; + break; + } + } + SendLeadershipEXPUpdate(); +} + +bool Client::IsLeadershipEXPOn() +{ + + if(!m_pp.leadAAActive) + return false; + + Group *g = GetGroup(); + + if(g && g->IsLeader(this) && (g->GroupCount() > 2)) + return true; + + Raid *r = GetRaid(); + + if(r && r->IsLeader(this) && (r->RaidCount() > 17)) + return true; + + return false; + +} + +int Client::GetAggroCount() { + return AggroCount; +} + +void Client::IncrementAggroCount() { + + // This method is called when a client is added to a mob's hate list. It turns the clients aggro flag on so + // rest state regen is stopped, and for SoF, it sends the opcode to show the crossed swords in-combat indicator. + // + // + AggroCount++; + + if(!RuleI(Character, RestRegenPercent)) + return; + + // If we already had aggro before this method was called, the combat indicator should already be up for SoF clients, + // so we don't need to send it again. + // + if(AggroCount > 1) + return; + + // Pause the rest timer + if (AggroCount == 1) + SavedRaidRestTimer = rest_timer.GetRemainingTime(); + + if(GetClientVersion() >= EQClientSoF) { + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_RestState, 1); + char *Buffer = (char *)outapp->pBuffer; + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0x01); + QueuePacket(outapp); + safe_delete(outapp); + } + +} + +void Client::DecrementAggroCount() { + + // This should be called when a client is removed from a mob's hate list (it dies or is memblurred). + // It checks whether any other mob is aggro on the player, and if not, starts the rest timer. + // For SoF, the opcode to start the rest state countdown timer in the UI is sent. + // + + // If we didn't have aggro before, this method should not have been called. + if(!AggroCount) + return; + + AggroCount--; + + if(!RuleI(Character, RestRegenPercent)) + return; + + // Something else is still aggro on us, can't rest yet. + if(AggroCount) return; + + uint32 time_until_rest; + if (GetEngagedRaidTarget()) { + time_until_rest = RuleI(Character, RestRegenRaidTimeToActivate) * 1000; + SetEngagedRaidTarget(false); + } else { + if (SavedRaidRestTimer > (RuleI(Character, RestRegenTimeToActivate) * 1000)) { + time_until_rest = SavedRaidRestTimer; + SavedRaidRestTimer = 0; + } else { + time_until_rest = RuleI(Character, RestRegenTimeToActivate) * 1000; + } + } + + rest_timer.Start(time_until_rest); + + if(GetClientVersion() >= EQClientSoF) { + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_RestState, 5); + char *Buffer = (char *)outapp->pBuffer; + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0x00); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, (uint32)(time_until_rest / 1000)); + QueuePacket(outapp); + safe_delete(outapp); + } +} + +void Client::SendPVPStats() +{ + // This sends the data to the client to populate the PVP Stats Window. + // + // When the PVP Stats window is opened, no opcode is sent. Therefore this method should be called + // from Client::CompleteConnect, and also when the player makes a PVP kill. + // + EQApplicationPacket *outapp = new EQApplicationPacket(OP_PVPStats, sizeof(PVPStats_Struct)); + PVPStats_Struct *pvps = (PVPStats_Struct *)outapp->pBuffer; + + pvps->Kills = m_pp.PVPKills; + pvps->Deaths = m_pp.PVPDeaths; + pvps->PVPPointsAvailable = m_pp.PVPCurrentPoints; + pvps->TotalPVPPoints = m_pp.PVPCareerPoints; + pvps->BestKillStreak = m_pp.PVPBestKillStreak; + pvps->WorstDeathStreak = m_pp.PVPWorstDeathStreak; + pvps->CurrentKillStreak = m_pp.PVPCurrentKillStreak; + + // TODO: Record and send other PVP Stats + + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::SendCrystalCounts() +{ + EQApplicationPacket *outapp = new EQApplicationPacket(OP_CrystalCountUpdate, sizeof(CrystalCountUpdate_Struct)); + CrystalCountUpdate_Struct *ccus = (CrystalCountUpdate_Struct *)outapp->pBuffer; + + ccus->CurrentRadiantCrystals = GetRadiantCrystals(); + ccus->CurrentEbonCrystals = GetEbonCrystals(); + ccus->CareerRadiantCrystals = m_pp.careerRadCrystals; + ccus->CareerEbonCrystals = m_pp.careerEbonCrystals; + + + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::SendDisciplineTimers() +{ + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_DisciplineTimer, sizeof(DisciplineTimer_Struct)); + DisciplineTimer_Struct *dts = (DisciplineTimer_Struct *)outapp->pBuffer; + + for(unsigned int i = 0; i < MAX_DISCIPLINE_TIMERS; ++i) + { + uint32 RemainingTime = p_timers.GetRemainingTime(pTimerDisciplineReuseStart + i); + + if(RemainingTime > 0) + { + dts->TimerID = i; + dts->Duration = RemainingTime; + QueuePacket(outapp); + } + } + + safe_delete(outapp); +} + +void Client::SendRespawnBinds() +{ + // This sends the data to the client to populate the Respawn from Death Window. + // + // This should be sent after OP_Death for SoF clients + // Client will respond with a 4 byte packet that includes the number of the selection made + // + + //If no options have been given, default to Bind + Rez + if (respawn_options.empty()) + { + BindStruct* b = &m_pp.binds[0]; + RespawnOption opt; + opt.name = "Bind Location"; + opt.zoneid = b->zoneId; + opt.x = b->x; + opt.y = b->y; + opt.z = b->z; + opt.heading = b->heading; + respawn_options.push_front(opt); + } + //Rez is always added at the end + RespawnOption rez; + rez.name = "Resurrect"; + rez.zoneid = zone->GetZoneID(); + rez.x = GetX(); + rez.y = GetY(); + rez.z = GetZ(); + rez.heading = GetHeading(); + respawn_options.push_back(rez); + + int num_options = respawn_options.size(); + uint32 PacketLength = 17 + (26 * num_options); //Header size + per-option invariant size + + std::list::iterator itr; + RespawnOption* opt; + + //Find string size for each option + for (itr = respawn_options.begin(); itr != respawn_options.end(); ++itr) + { + opt = &(*itr); + PacketLength += opt->name.size() + 1; //+1 for cstring + } + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_RespawnWindow, PacketLength); + char* buffer = (char*)outapp->pBuffer; + + //Packet header + VARSTRUCT_ENCODE_TYPE(uint32, buffer, initial_respawn_selection); //initial selection (from 0) + VARSTRUCT_ENCODE_TYPE(uint32, buffer, RuleI(Character, RespawnFromHoverTimer) * 1000); + VARSTRUCT_ENCODE_TYPE(uint32, buffer, 0); //unknown + VARSTRUCT_ENCODE_TYPE(uint32, buffer, num_options); //number of options to display + + //Individual options + int count = 0; + for (itr = respawn_options.begin(); itr != respawn_options.end(); ++itr) + { + opt = &(*itr); + VARSTRUCT_ENCODE_TYPE(uint32, buffer, count++); //option num (from 0) + VARSTRUCT_ENCODE_TYPE(uint32, buffer, opt->zoneid); + VARSTRUCT_ENCODE_TYPE(float, buffer, opt->x); + VARSTRUCT_ENCODE_TYPE(float, buffer, opt->y); + VARSTRUCT_ENCODE_TYPE(float, buffer, opt->z); + VARSTRUCT_ENCODE_TYPE(float, buffer, opt->heading); + VARSTRUCT_ENCODE_STRING(buffer, opt->name.c_str()); + VARSTRUCT_ENCODE_TYPE(uint8, buffer, (count == num_options)); //is this one Rez (the last option)? + } + + QueuePacket(outapp); + safe_delete(outapp); + return; +} + +void Client::HandleLDoNOpen(NPC *target) +{ + if(target) + { + if(target->GetClass() != LDON_TREASURE) + { + LogFile->write(EQEMuLog::Debug, "%s tried to open %s but %s was not a treasure chest.", + GetName(), target->GetName(), target->GetName()); + return; + } + + if(DistNoRootNoZ(*target) > RuleI(Adventure, LDoNTrapDistanceUse)) + { + LogFile->write(EQEMuLog::Debug, "%s tried to open %s but %s was out of range", + GetName(), target->GetName(), target->GetName()); + Message(13, "Treasure chest out of range."); + return; + } + + if(target->IsLDoNTrapped()) + { + if(target->GetLDoNTrapSpellID() != 0) + { + Message_StringID(13, LDON_ACCIDENT_SETOFF2); + target->SpellFinished(target->GetLDoNTrapSpellID(), this, 10, 0, -1, spells[target->GetLDoNTrapSpellID()].ResistDiff); + target->SetLDoNTrapSpellID(0); + target->SetLDoNTrapped(false); + target->SetLDoNTrapDetected(false); + } + else + { + target->SetLDoNTrapSpellID(0); + target->SetLDoNTrapped(false); + target->SetLDoNTrapDetected(false); + } + } + + if(target->IsLDoNLocked()) + { + Message_StringID(MT_Skills, LDON_STILL_LOCKED, target->GetCleanName()); + return; + } + else + { + target->AddToHateList(this, 0, 500000, false, false, false); + if(target->GetLDoNTrapType() != 0) + { + if(GetRaid()) + { + GetRaid()->SplitExp(target->GetLevel()*target->GetLevel()*2625/10, target); + } + else if(GetGroup()) + { + GetGroup()->SplitExp(target->GetLevel()*target->GetLevel()*2625/10, target); + } + else + { + AddEXP(target->GetLevel()*target->GetLevel()*2625/10, GetLevelCon(target->GetLevel())); + } + } + target->Death(this, 1, SPELL_UNKNOWN, SkillHandtoHand); + } + } +} + +void Client::HandleLDoNSenseTraps(NPC *target, uint16 skill, uint8 type) +{ + if(target && target->GetClass() == LDON_TREASURE) + { + if(target->IsLDoNTrapped()) + { + if((target->GetLDoNTrapType() == LDoNTypeCursed || target->GetLDoNTrapType() == LDoNTypeMagical) && type != target->GetLDoNTrapType()) + { + Message_StringID(MT_Skills, LDON_CANT_DETERMINE_TRAP, target->GetCleanName()); + return; + } + + if(target->IsLDoNTrapDetected()) + { + Message_StringID(MT_Skills, LDON_CERTAIN_TRAP, target->GetCleanName()); + } + else + { + int check = LDoNChest_SkillCheck(target, skill); + switch(check) + { + case -1: + case 0: + Message_StringID(MT_Skills, LDON_DONT_KNOW_TRAPPED, target->GetCleanName()); + break; + case 1: + Message_StringID(MT_Skills, LDON_CERTAIN_TRAP, target->GetCleanName()); + target->SetLDoNTrapDetected(true); + break; + default: + break; + } + } + } + else + { + Message_StringID(MT_Skills, LDON_CERTAIN_NOT_TRAP, target->GetCleanName()); + } + } +} + +void Client::HandleLDoNDisarm(NPC *target, uint16 skill, uint8 type) +{ + if(target) + { + if(target->GetClass() == LDON_TREASURE) + { + if(!target->IsLDoNTrapped()) + { + Message_StringID(MT_Skills, LDON_WAS_NOT_TRAPPED, target->GetCleanName()); + return; + } + + if((target->GetLDoNTrapType() == LDoNTypeCursed || target->GetLDoNTrapType() == LDoNTypeMagical) && type != target->GetLDoNTrapType()) + { + Message_StringID(MT_Skills, LDON_HAVE_NOT_DISARMED, target->GetCleanName()); + return; + } + + int check = 0; + if(target->IsLDoNTrapDetected()) + { + check = LDoNChest_SkillCheck(target, skill); + } + else + { + check = LDoNChest_SkillCheck(target, skill*33/100); + } + switch(check) + { + case 1: + target->SetLDoNTrapDetected(false); + target->SetLDoNTrapped(false); + target->SetLDoNTrapSpellID(0); + Message_StringID(MT_Skills, LDON_HAVE_DISARMED, target->GetCleanName()); + break; + case 0: + Message_StringID(MT_Skills, LDON_HAVE_NOT_DISARMED, target->GetCleanName()); + break; + case -1: + Message_StringID(13, LDON_ACCIDENT_SETOFF2); + target->SpellFinished(target->GetLDoNTrapSpellID(), this, 10, 0, -1, spells[target->GetLDoNTrapSpellID()].ResistDiff); + target->SetLDoNTrapSpellID(0); + target->SetLDoNTrapped(false); + target->SetLDoNTrapDetected(false); + break; + } + } + } +} + +void Client::HandleLDoNPickLock(NPC *target, uint16 skill, uint8 type) +{ + if(target) + { + if(target->GetClass() == LDON_TREASURE) + { + if(target->IsLDoNTrapped()) + { + Message_StringID(13, LDON_ACCIDENT_SETOFF2); + target->SpellFinished(target->GetLDoNTrapSpellID(), this, 10, 0, -1, spells[target->GetLDoNTrapSpellID()].ResistDiff); + target->SetLDoNTrapSpellID(0); + target->SetLDoNTrapped(false); + target->SetLDoNTrapDetected(false); + } + + if(!target->IsLDoNLocked()) + { + Message_StringID(MT_Skills, LDON_WAS_NOT_LOCKED, target->GetCleanName()); + return; + } + + if((target->GetLDoNTrapType() == LDoNTypeCursed || target->GetLDoNTrapType() == LDoNTypeMagical) && type != target->GetLDoNTrapType()) + { + Message(MT_Skills, "You cannot unlock %s with this skill.", target->GetCleanName()); + return; + } + + int check = LDoNChest_SkillCheck(target, skill); + + switch(check) + { + case 0: + case -1: + Message_StringID(MT_Skills, LDON_PICKLOCK_FAILURE, target->GetCleanName()); + break; + case 1: + target->SetLDoNLocked(false); + Message_StringID(MT_Skills, LDON_PICKLOCK_SUCCESS, target->GetCleanName()); + break; + } + } + } +} + +int Client::LDoNChest_SkillCheck(NPC *target, int skill) +{ + if(!target) + return -1; + + int chest_difficulty = target->GetLDoNLockedSkill() == 0 ? (target->GetLevel() * 5) : target->GetLDoNLockedSkill(); + float base_difficulty = RuleR(Adventure, LDoNBaseTrapDifficulty); + + if(chest_difficulty == 0) + chest_difficulty = 5; + + float chance = ((100.0f - base_difficulty) * ((float)skill / (float)chest_difficulty)); + + 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 + RuleR(Adventure, LDoNCriticalFailTrapThreshold))) + return -1; + } + + return 0; +} + +void Client::SummonAndRezzAllCorpses() +{ + PendingRezzXP = -1; + + ServerPacket *Pack = new ServerPacket(ServerOP_DepopAllPlayersCorpses, sizeof(ServerDepopAllPlayersCorpses_Struct)); + + ServerDepopAllPlayersCorpses_Struct *sdapcs = (ServerDepopAllPlayersCorpses_Struct*)Pack->pBuffer; + + sdapcs->CharacterID = CharacterID(); + sdapcs->ZoneID = zone->GetZoneID(); + sdapcs->InstanceID = zone->GetInstanceID(); + + worldserver.SendPacket(Pack); + + safe_delete(Pack); + + entity_list.RemoveAllCorpsesByCharID(CharacterID()); + + int CorpseCount = database.SummonAllPlayerCorpses(CharacterID(), zone->GetZoneID(), zone->GetInstanceID(), + GetX(), GetY(), GetZ(), GetHeading()); + if(CorpseCount <= 0) + { + Message(clientMessageYellow, "You have no corpses to summnon."); + return; + } + + int RezzExp = entity_list.RezzAllCorpsesByCharID(CharacterID()); + + if(RezzExp > 0) + SetEXP(GetEXP() + RezzExp, GetAAXP(), true); + + Message(clientMessageYellow, "All your corpses have been summoned to your feet and have received a 100% resurrection."); +} + +void Client::SummonAllCorpses(float dest_x, float dest_y, float dest_z, float dest_heading) +{ + + if(dest_x == 0 && dest_y == 0 && dest_z == 0 && dest_heading == 0) + { + dest_x = GetX(); dest_y = GetY(); dest_z = GetZ(); dest_heading = GetHeading(); + } + + ServerPacket *Pack = new ServerPacket(ServerOP_DepopAllPlayersCorpses, sizeof(ServerDepopAllPlayersCorpses_Struct)); + + ServerDepopAllPlayersCorpses_Struct *sdapcs = (ServerDepopAllPlayersCorpses_Struct*)Pack->pBuffer; + + sdapcs->CharacterID = CharacterID(); + sdapcs->ZoneID = zone->GetZoneID(); + sdapcs->InstanceID = zone->GetInstanceID(); + + worldserver.SendPacket(Pack); + + safe_delete(Pack); + + entity_list.RemoveAllCorpsesByCharID(CharacterID()); + + int CorpseCount = database.SummonAllPlayerCorpses(CharacterID(), zone->GetZoneID(), zone->GetInstanceID(), + dest_x, dest_y, dest_z, dest_heading); + if(CorpseCount <= 0) + { + return; + } +} + +void Client::DepopAllCorpses() +{ + ServerPacket *Pack = new ServerPacket(ServerOP_DepopAllPlayersCorpses, sizeof(ServerDepopAllPlayersCorpses_Struct)); + + ServerDepopAllPlayersCorpses_Struct *sdapcs = (ServerDepopAllPlayersCorpses_Struct*)Pack->pBuffer; + + sdapcs->CharacterID = CharacterID(); + sdapcs->ZoneID = zone->GetZoneID(); + sdapcs->InstanceID = zone->GetInstanceID(); + + worldserver.SendPacket(Pack); + + safe_delete(Pack); + + entity_list.RemoveAllCorpsesByCharID(CharacterID()); +} + +void Client::DepopPlayerCorpse(uint32 dbid) +{ + ServerPacket *Pack = new ServerPacket(ServerOP_DepopPlayerCorpse, sizeof(ServerDepopPlayerCorpse_Struct)); + + ServerDepopPlayerCorpse_Struct *sdpcs = (ServerDepopPlayerCorpse_Struct*)Pack->pBuffer; + + sdpcs->DBID = dbid; + sdpcs->ZoneID = zone->GetZoneID(); + sdpcs->InstanceID = zone->GetInstanceID(); + + worldserver.SendPacket(Pack); + + safe_delete(Pack); + + entity_list.RemoveCorpseByDBID(dbid); +} + +void Client::BuryPlayerCorpses() +{ + database.BuryAllPlayerCorpses(CharacterID()); +} + +void Client::NotifyNewTitlesAvailable() +{ + EQApplicationPacket *outapp = new EQApplicationPacket(OP_NewTitlesAvailable, 0); + + QueuePacket(outapp); + + safe_delete(outapp); + +} + +void Client::SetStartZone(uint32 zoneid, float x, float y, float z) +{ + // setting city to zero allows the player to use /setstartcity to set the city themselves + if(zoneid == 0) { + m_pp.binds[4].zoneId = 0; + this->Message(15,"Your starting city has been reset. Use /setstartcity to choose a new one"); + return; + } + + // check to make sure the zone is valid + const char *target_zone_name = database.GetZoneName(zoneid); + if(target_zone_name == nullptr) + return; + + m_pp.binds[4].zoneId = zoneid; + if (x == 0 && y == 0 && z ==0) + database.GetSafePoints(m_pp.binds[4].zoneId, 0, &m_pp.binds[4].x, &m_pp.binds[4].y, &m_pp.binds[4].z); + else { + m_pp.binds[4].x = x; + m_pp.binds[4].y = y; + m_pp.binds[4].z = z; + } +} + +uint32 Client::GetStartZone() +{ + return m_pp.binds[4].zoneId; +} + +void Client::ShowSkillsWindow() +{ + const char *WindowTitle = "Skills"; + std::string WindowText; + // using a map for easy alphabetizing of the skills list + std::map Skills; + std::map::iterator it; + + // this list of names must keep the same order as that in common/skills.h + const char* SkillName[] = {"1H Blunt","1H Slashing","2H Blunt","2H Slashing","Abjuration","Alteration","Apply Poison","Archery", + "Backstab","Bind Wound","Bash","Block","Brass Instruments","Channeling","Conjuration","Defense","Disarm","Disarm Traps","Divination", + "Dodge","Double Attack","Dragon Punch","Dual Wield","Eagle Strike","Evocation","Feign Death","Flying Kick","Forage","Hand to Hand", + "Hide","Kick","Meditate","Mend","Offense","Parry","Pick Lock","Piercing","Ripost","Round Kick","Safe Fall","Sense Heading", + "Singing","Sneak","Specialize Abjuration","Specialize Alteration","Specialize Conjuration","Specialize Divination","Specialize Evocation","Pick Pockets", + "Stringed Instruments","Swimming","Throwing","Tiger Claw","Tracking","Wind Instruments","Fishing","Make Poison","Tinkering","Research", + "Alchemy","Baking","Tailoring","Sense Traps","Blacksmithing","Fletching","Brewing","Alcohol Tolerance","Begging","Jewelry Making", + "Pottery","Percussion Instruments","Intimidation","Berserking","Taunt","Frenzy"}; + for(int i = 0; i <= (int)HIGHEST_SKILL; i++) + Skills[SkillName[i]] = (SkillUseTypes)i; + + // print out all available skills + for(it = Skills.begin(); it != Skills.end(); ++it) { + if(GetSkill(it->second) > 0 || MaxSkill(it->second) > 0) { + WindowText += it->first; + // line up the values + for (int j = 0; j < EmuConstants::ITEM_COMMON_SIZE; j++) + WindowText += " "; + WindowText += itoa(this->GetSkill(it->second)); + if (MaxSkill(it->second) > 0) { + WindowText += "/"; + WindowText += itoa(this->GetMaxSkillAfterSpecializationRules(it->second,this->MaxSkill(it->second))); + } + WindowText += "
"; + } + } + this->SendPopupToClient(WindowTitle, WindowText.c_str()); +} + + +void Client::SetShadowStepExemption(bool v) +{ + if(v == true) + { + uint32 cur_time = Timer::GetCurrentTime(); + if((cur_time - m_TimeSinceLastPositionCheck) > 1000) + { + float speed = (m_DistanceSinceLastPositionCheck * 100) / (float)(cur_time - m_TimeSinceLastPositionCheck); + float runs = GetRunspeed(); + if(speed > (runs * RuleR(Zone, MQWarpDetectionDistanceFactor))) + { + printf("%s %i moving too fast! moved: %.2f in %ims, speed %.2f\n", __FILE__, __LINE__, + m_DistanceSinceLastPositionCheck, (cur_time - m_TimeSinceLastPositionCheck), speed); + if(!GetGMSpeed() && (runs >= GetBaseRunspeed() || (speed > (GetBaseRunspeed() * RuleR(Zone, MQWarpDetectionDistanceFactor))))) + { + if(IsShadowStepExempted()) + { + if(m_DistanceSinceLastPositionCheck > 800) + { + CheatDetected(MQWarpShadowStep, GetX(), GetY(), GetZ()); + } + } + else if(IsKnockBackExempted()) + { + //still potential to trigger this if you're knocked back off a + //HUGE fall that takes > 2.5 seconds + if(speed > 30.0f) + { + CheatDetected(MQWarpKnockBack, GetX(), GetY(), GetZ()); + } + } + else if(!IsPortExempted()) + { + if(!IsMQExemptedArea(zone->GetZoneID(), GetX(), GetY(), GetZ())) + { + if(speed > (runs * 2 * RuleR(Zone, MQWarpDetectionDistanceFactor))) + { + CheatDetected(MQWarp, GetX(), GetY(), GetZ()); + m_TimeSinceLastPositionCheck = cur_time; + m_DistanceSinceLastPositionCheck = 0.0f; + //Death(this, 10000000, SPELL_UNKNOWN, _1H_BLUNT); + } + else + { + CheatDetected(MQWarpLight, GetX(), GetY(), GetZ()); + } + } + } + } + } + } + m_TimeSinceLastPositionCheck = cur_time; + m_DistanceSinceLastPositionCheck = 0.0f; + } + m_ShadowStepExemption = v; +} + +void Client::SetKnockBackExemption(bool v) +{ + if(v == true) + { + uint32 cur_time = Timer::GetCurrentTime(); + if((cur_time - m_TimeSinceLastPositionCheck) > 1000) + { + float speed = (m_DistanceSinceLastPositionCheck * 100) / (float)(cur_time - m_TimeSinceLastPositionCheck); + float runs = GetRunspeed(); + if(speed > (runs * RuleR(Zone, MQWarpDetectionDistanceFactor))) + { + if(!GetGMSpeed() && (runs >= GetBaseRunspeed() || (speed > (GetBaseRunspeed() * RuleR(Zone, MQWarpDetectionDistanceFactor))))) + { + printf("%s %i moving too fast! moved: %.2f in %ims, speed %.2f\n", __FILE__, __LINE__, + m_DistanceSinceLastPositionCheck, (cur_time - m_TimeSinceLastPositionCheck), speed); + if(IsShadowStepExempted()) + { + if(m_DistanceSinceLastPositionCheck > 800) + { + CheatDetected(MQWarpShadowStep, GetX(), GetY(), GetZ()); + } + } + else if(IsKnockBackExempted()) + { + //still potential to trigger this if you're knocked back off a + //HUGE fall that takes > 2.5 seconds + if(speed > 30.0f) + { + CheatDetected(MQWarpKnockBack, GetX(), GetY(), GetZ()); + } + } + else if(!IsPortExempted()) + { + if(!IsMQExemptedArea(zone->GetZoneID(), GetX(), GetY(), GetZ())) + { + if(speed > (runs * 2 * RuleR(Zone, MQWarpDetectionDistanceFactor))) + { + m_TimeSinceLastPositionCheck = cur_time; + m_DistanceSinceLastPositionCheck = 0.0f; + CheatDetected(MQWarp, GetX(), GetY(), GetZ()); + //Death(this, 10000000, SPELL_UNKNOWN, _1H_BLUNT); + } + else + { + CheatDetected(MQWarpLight, GetX(), GetY(), GetZ()); + } + } + } + } + } + } + m_TimeSinceLastPositionCheck = cur_time; + m_DistanceSinceLastPositionCheck = 0.0f; + } + m_KnockBackExemption = v; +} + +void Client::SetPortExemption(bool v) +{ + if(v == true) + { + uint32 cur_time = Timer::GetCurrentTime(); + if((cur_time - m_TimeSinceLastPositionCheck) > 1000) + { + float speed = (m_DistanceSinceLastPositionCheck * 100) / (float)(cur_time - m_TimeSinceLastPositionCheck); + float runs = GetRunspeed(); + if(speed > (runs * RuleR(Zone, MQWarpDetectionDistanceFactor))) + { + if(!GetGMSpeed() && (runs >= GetBaseRunspeed() || (speed > (GetBaseRunspeed() * RuleR(Zone, MQWarpDetectionDistanceFactor))))) + { + printf("%s %i moving too fast! moved: %.2f in %ims, speed %.2f\n", __FILE__, __LINE__, + m_DistanceSinceLastPositionCheck, (cur_time - m_TimeSinceLastPositionCheck), speed); + if(IsShadowStepExempted()) + { + if(m_DistanceSinceLastPositionCheck > 800) + { + CheatDetected(MQWarpShadowStep, GetX(), GetY(), GetZ()); + } + } + else if(IsKnockBackExempted()) + { + //still potential to trigger this if you're knocked back off a + //HUGE fall that takes > 2.5 seconds + if(speed > 30.0f) + { + CheatDetected(MQWarpKnockBack, GetX(), GetY(), GetZ()); + } + } + else if(!IsPortExempted()) + { + if(!IsMQExemptedArea(zone->GetZoneID(), GetX(), GetY(), GetZ())) + { + if(speed > (runs * 2 * RuleR(Zone, MQWarpDetectionDistanceFactor))) + { + m_TimeSinceLastPositionCheck = cur_time; + m_DistanceSinceLastPositionCheck = 0.0f; + CheatDetected(MQWarp, GetX(), GetY(), GetZ()); + //Death(this, 10000000, SPELL_UNKNOWN, _1H_BLUNT); + } + else + { + CheatDetected(MQWarpLight, GetX(), GetY(), GetZ()); + } + } + } + } + } + } + m_TimeSinceLastPositionCheck = cur_time; + m_DistanceSinceLastPositionCheck = 0.0f; + } + m_PortExemption = v; +} + +void Client::Signal(uint32 data) +{ + char buf[32]; + snprintf(buf, 31, "%d", data); + buf[31] = '\0'; + parse->EventPlayer(EVENT_SIGNAL, this, buf, 0); +} + +const bool Client::IsMQExemptedArea(uint32 zoneID, float x, float y, float z) const +{ + float max_dist = 90000; + switch(zoneID) + { + case 2: + { + float delta = (x-(-713.6)); + delta *= delta; + float distance = delta; + delta = (y-(-160.2)); + delta *= delta; + distance += delta; + delta = (z-(-12.8)); + delta *= delta; + distance += delta; + + if(distance < max_dist) + return true; + + delta = (x-(-153.8)); + delta *= delta; + distance = delta; + delta = (y-(-30.3)); + delta *= delta; + distance += delta; + delta = (z-(8.2)); + delta *= delta; + distance += delta; + + if(distance < max_dist) + return true; + + break; + } + case 9: + { + float delta = (x-(-682.5)); + delta *= delta; + float distance = delta; + delta = (y-(147.0)); + delta *= delta; + distance += delta; + delta = (z-(-9.9)); + delta *= delta; + distance += delta; + + if(distance < max_dist) + return true; + + delta = (x-(-655.4)); + delta *= delta; + distance = delta; + delta = (y-(10.5)); + delta *= delta; + distance += delta; + delta = (z-(-51.8)); + delta *= delta; + distance += delta; + + if(distance < max_dist) + return true; + + break; + } + case 62: + case 75: + case 114: + case 209: + { + //The portals are so common in paineel/felwitheb that checking + //distances wouldn't be worth it cause unless you're porting to the + //start field you're going to be triggering this and that's a level of + //accuracy I'm willing to sacrifice + return true; + break; + } + + case 24: + { + float delta = (x-(-183.0)); + delta *= delta; + float distance = delta; + delta = (y-(-773.3)); + delta *= delta; + distance += delta; + delta = (z-(54.1)); + delta *= delta; + distance += delta; + + if(distance < max_dist) + return true; + + delta = (x-(-8.8)); + delta *= delta; + distance = delta; + delta = (y-(-394.1)); + delta *= delta; + distance += delta; + delta = (z-(41.1)); + delta *= delta; + distance += delta; + + if(distance < max_dist) + return true; + + delta = (x-(-310.3)); + delta *= delta; + distance = delta; + delta = (y-(-1411.6)); + delta *= delta; + distance += delta; + delta = (z-(-42.8)); + delta *= delta; + distance += delta; + + if(distance < max_dist) + return true; + + delta = (x-(-183.1)); + delta *= delta; + distance = delta; + delta = (y-(-1409.8)); + delta *= delta; + distance += delta; + delta = (z-(37.1)); + delta *= delta; + distance += delta; + + if(distance < max_dist) + return true; + + break; + } + + case 110: + case 34: + case 96: + case 93: + case 68: + case 84: + { + if(GetBoatID() != 0) + return true; + break; + } + default: + break; + } + return false; +} + +void Client::SendRewards() +{ + std::vector rewards; + std::string query = StringFormat("SELECT reward_id, amount FROM " + "account_rewards WHERE account_id = %i " + "ORDER BY reward_id", AccountID()); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in Client::SendRewards(): %s (%s)", query.c_str(), results.ErrorMessage().c_str()); + return; + } + + for (auto row = results.begin(); row != results.end(); ++row) { + ClientReward cr; + cr.id = atoi(row[0]); + cr.amount = atoi(row[1]); + rewards.push_back(cr); + } + + if(rewards.size() > 0) + { + EQApplicationPacket *vetapp = new EQApplicationPacket(OP_VetRewardsAvaliable, (sizeof(InternalVeteranReward) * rewards.size())); + uchar *data = vetapp->pBuffer; + for(int i = 0; i < rewards.size(); ++i) + { + InternalVeteranReward *ivr = (InternalVeteranReward*)data; + ivr->claim_id = rewards[i].id; + ivr->number_available = rewards[i].amount; + std::list::iterator iter = zone->VeteranRewards.begin(); + while(iter != zone->VeteranRewards.end()) + { + if((*iter).claim_id == rewards[i].id) + { + break; + } + ++iter; + } + + if(iter != zone->VeteranRewards.end()) + { + InternalVeteranReward ivro = (*iter); + ivr->claim_count = ivro.claim_count; + for(int x = 0; x < ivro.claim_count; ++x) + { + ivr->items[x].item_id = ivro.items[x].item_id; + ivr->items[x].charges = ivro.items[x].charges; + strcpy(ivr->items[x].item_name, ivro.items[x].item_name); + } + } + + data += sizeof(InternalVeteranReward); + } + FastQueuePacket(&vetapp); + } +} + +bool Client::TryReward(uint32 claim_id) +{ + //Make sure we have an open spot + //Make sure we have it in our acct and count > 0 + //Make sure the entry was found + //If we meet all the criteria: + //Decrement our count by 1 if it > 1 delete if it == 1 + //Create our item in bag if necessary at the free inv slot + //save + uint32 free_slot = 0xFFFFFFFF; + + for(int i = EmuConstants::GENERAL_BEGIN; i <= EmuConstants::GENERAL_END; ++i) + { + ItemInst *item = GetInv().GetItem(i); + if(!item) + { + free_slot = i; + break; + } + } + + if(free_slot == 0xFFFFFFFF) + return false; + + std::string query = StringFormat("SELECT amount FROM account_rewards " + "WHERE account_id=%i AND reward_id=%i", + AccountID(), claim_id); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in Client::TryReward(): %s (%s)", query.c_str(), results.ErrorMessage().c_str()); + return false; + } + + if (results.RowCount() == 0) + return false; + + uint32 amt = 0; + auto row = results.begin(); + + amt = atoi(row[0]); + + if(amt == 0) + return false; + + auto iter = zone->VeteranRewards.begin(); + for (; iter != zone->VeteranRewards.end(); ++iter) + if((*iter).claim_id == claim_id) + break; + + if(iter == zone->VeteranRewards.end()) + return false; + + if(amt == 1) + { + query = StringFormat("DELETE FROM account_rewards " + "WHERE account_id=%i AND reward_id=%i", + AccountID(), claim_id); + results = database.QueryDatabase(query); + if(!results.ErrorMessage().c_str()) + LogFile->write(EQEMuLog::Error, "Error in Client::TryReward(): %s (%s)", query.c_str(), results.ErrorMessage().c_str()); + + } + else + { + query = StringFormat("UPDATE account_rewards SET amount=(amount-1) " + "WHERE account_id=%i AND reward_id=%i", AccountID(), claim_id); + results = database.QueryDatabase(query); + if(!results.Success()) + LogFile->write(EQEMuLog::Error, "Error in Client::TryReward(): %s (%s)", query.c_str(), results.ErrorMessage().c_str()); + + } + + InternalVeteranReward ivr = (*iter); + ItemInst *claim = database.CreateItem(ivr.items[0].item_id, ivr.items[0].charges); + if(claim) + { + bool lore_conflict = false; + if(CheckLoreConflict(claim->GetItem())) + { + lore_conflict = true; + } + + for(int y = 1; y < 8; y++) + { + if(ivr.items[y].item_id) + { + if(claim->GetItem()->ItemClass == 1) + { + ItemInst *item_temp = database.CreateItem(ivr.items[y].item_id, ivr.items[y].charges); + if(item_temp) + { + if(CheckLoreConflict(item_temp->GetItem())) + { + lore_conflict = true; + DuplicateLoreMessage(ivr.items[y].item_id); + } + claim->PutItem(y-1, *item_temp); + } + } + } + } + + if(lore_conflict) + { + safe_delete(claim); + return true; + } + else + { + PutItemInInventory(free_slot, *claim); + SendItemPacket(free_slot, claim, ItemPacketTrade); + } + } + + Save(); + return true; +} + +uint32 Client::GetLDoNPointsTheme(uint32 t) +{ + switch(t) + { + case 1: + return m_pp.ldon_points_guk; + case 2: + return m_pp.ldon_points_mir; + case 3: + return m_pp.ldon_points_mmc; + case 4: + return m_pp.ldon_points_ruj; + case 5: + return m_pp.ldon_points_tak; + default: + return 0; + } +} + +uint32 Client::GetLDoNWinsTheme(uint32 t) +{ + switch(t) + { + case 1: + return m_pp.ldon_wins_guk; + case 2: + return m_pp.ldon_wins_mir; + case 3: + return m_pp.ldon_wins_mmc; + case 4: + return m_pp.ldon_wins_ruj; + case 5: + return m_pp.ldon_wins_tak; + default: + return 0; + } +} + +uint32 Client::GetLDoNLossesTheme(uint32 t) +{ + switch(t) + { + case 1: + return m_pp.ldon_losses_guk; + case 2: + return m_pp.ldon_losses_mir; + case 3: + return m_pp.ldon_losses_mmc; + case 4: + return m_pp.ldon_losses_ruj; + case 5: + return m_pp.ldon_losses_tak; + default: + return 0; + } +} + +void Client::UpdateLDoNWins(uint32 t, int32 n) +{ + switch(t) + { + case 1: + m_pp.ldon_wins_guk = n; + break; + case 2: + m_pp.ldon_wins_mir = n; + break; + case 3: + m_pp.ldon_wins_mmc = n; + break; + case 4: + m_pp.ldon_wins_ruj = n; + break; + case 5: + m_pp.ldon_wins_tak = n; + break; + default: + return; + } +} + +void Client::UpdateLDoNLosses(uint32 t, int32 n) +{ + switch(t) + { + case 1: + m_pp.ldon_losses_guk = n; + break; + case 2: + m_pp.ldon_losses_mir = n; + break; + case 3: + m_pp.ldon_losses_mmc = n; + break; + case 4: + m_pp.ldon_losses_ruj = n; + break; + case 5: + m_pp.ldon_losses_tak = n; + break; + default: + return; + } +} + + +void Client::SuspendMinion() +{ + NPC *CurrentPet = GetPet()->CastToNPC(); + + int AALevel = GetAA(aaSuspendedMinion); + + if(AALevel == 0) + return; + + if(GetLevel() < 62) + return; + + if(!CurrentPet) + { + if(m_suspendedminion.SpellID > 0) + { + MakePoweredPet(m_suspendedminion.SpellID, spells[m_suspendedminion.SpellID].teleport_zone, + m_suspendedminion.petpower, m_suspendedminion.Name, m_suspendedminion.size); + + CurrentPet = GetPet()->CastToNPC(); + + if(!CurrentPet) + { + Message(13, "Failed to recall suspended minion."); + return; + } + + if(AALevel >= 2) + { + CurrentPet->SetPetState(m_suspendedminion.Buffs, m_suspendedminion.Items); + + CurrentPet->SendPetBuffsToClient(); + } + CurrentPet->CalcBonuses(); + + CurrentPet->SetHP(m_suspendedminion.HP); + + CurrentPet->SetMana(m_suspendedminion.Mana); + + Message_StringID(clientMessageTell, SUSPEND_MINION_UNSUSPEND, CurrentPet->GetCleanName()); + + memset(&m_suspendedminion, 0, sizeof(struct PetInfo)); + } + else + return; + + } + else + { + uint16 SpellID = CurrentPet->GetPetSpellID(); + + if(SpellID) + { + if(m_suspendedminion.SpellID > 0) + { + Message_StringID(clientMessageError,ONLY_ONE_PET); + + return; + } + else if(CurrentPet->IsEngaged()) + { + Message_StringID(clientMessageError,SUSPEND_MINION_FIGHTING); + + return; + } + else if(entity_list.Fighting(CurrentPet)) + { + Message_StringID(clientMessageBlue,SUSPEND_MINION_HAS_AGGRO); + } + else + { + m_suspendedminion.SpellID = SpellID; + + m_suspendedminion.HP = CurrentPet->GetHP();; + + m_suspendedminion.Mana = CurrentPet->GetMana(); + m_suspendedminion.petpower = CurrentPet->GetPetPower(); + m_suspendedminion.size = CurrentPet->GetSize(); + + if(AALevel >= 2) + CurrentPet->GetPetState(m_suspendedminion.Buffs, m_suspendedminion.Items, m_suspendedminion.Name); + else + strn0cpy(m_suspendedminion.Name, CurrentPet->GetName(), 64); // Name stays even at rank 1 + + Message_StringID(clientMessageTell, SUSPEND_MINION_SUSPEND, CurrentPet->GetCleanName()); + + CurrentPet->Depop(false); + + SetPetID(0); + } + } + else + { + Message_StringID(clientMessageError, ONLY_SUMMONED_PETS); + + return; + } + } +} + +void Client::AddPVPPoints(uint32 Points) +{ + m_pp.PVPCurrentPoints += Points; + m_pp.PVPCareerPoints += Points; + + Save(); + + SendPVPStats(); +} + +void Client::AddCrystals(uint32 Radiant, uint32 Ebon) +{ + m_pp.currentRadCrystals += Radiant; + m_pp.careerRadCrystals += Radiant; + m_pp.currentEbonCrystals += Ebon; + m_pp.careerEbonCrystals += Ebon; + + Save(); + + SendCrystalCounts(); +} + +// Processes a client request to inspect a SoF+ client's equipment. +void Client::ProcessInspectRequest(Client* requestee, Client* requester) { + if(requestee && requester) { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_InspectAnswer, sizeof(InspectResponse_Struct)); + InspectResponse_Struct* insr = (InspectResponse_Struct*) outapp->pBuffer; + insr->TargetID = requester->GetID(); + insr->playerid = requestee->GetID(); + + const Item_Struct* item = nullptr; + const ItemInst* inst = nullptr; + + for(int16 L = 0; L <= 20; L++) { + inst = requestee->GetInv().GetItem(L); + + if(inst) { + item = inst->GetItem(); + if(item) { + strcpy(insr->itemnames[L], item->Name); + insr->itemicons[L] = item->Icon; + } + else + insr->itemicons[L] = 0xFFFFFFFF; + } + } + + inst = requestee->GetInv().GetItem(MainPowerSource); + + if(inst) { + item = inst->GetItem(); + if(item) { + // we shouldn't do this..but, that's the way it's coded atm... + // (this type of action should be handled exclusively in the client translator) + strcpy(insr->itemnames[SoF::slots::MainPowerSource], item->Name); + insr->itemicons[SoF::slots::MainPowerSource] = item->Icon; + } + else + insr->itemicons[SoF::slots::MainPowerSource] = 0xFFFFFFFF; + } + + inst = requestee->GetInv().GetItem(MainAmmo); + + if(inst) { + item = inst->GetItem(); + if(item) { + strcpy(insr->itemnames[SoF::slots::MainAmmo], item->Name); + insr->itemicons[SoF::slots::MainAmmo] = item->Icon; + } + else + insr->itemicons[SoF::slots::MainAmmo] = 0xFFFFFFFF; + } + + strcpy(insr->text, requestee->GetInspectMessage().text); + + // There could be an OP for this..or not... (Ti clients are not processed here..this message is generated client-side) + if(requestee->IsClient() && (requestee != requester)) { requestee->Message(0, "%s is looking at your equipment...", requester->GetName()); } + + requester->QueuePacket(outapp); // Send answer to requester + safe_delete(outapp); + } +} + +void Client::GuildBankAck() +{ + EQApplicationPacket *outapp = new EQApplicationPacket(OP_GuildBank, sizeof(GuildBankAck_Struct)); + + GuildBankAck_Struct *gbas = (GuildBankAck_Struct*) outapp->pBuffer; + + gbas->Action = GuildBankAcknowledge; + + FastQueuePacket(&outapp); +} + +void Client::GuildBankDepositAck(bool Fail) +{ + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_GuildBank, sizeof(GuildBankDepositAck_Struct)); + + GuildBankDepositAck_Struct *gbdas = (GuildBankDepositAck_Struct*) outapp->pBuffer; + + gbdas->Action = GuildBankDeposit; + + gbdas->Fail = Fail ? 1 : 0; + + FastQueuePacket(&outapp); +} + +void Client::ClearGuildBank() +{ + EQApplicationPacket *outapp = new EQApplicationPacket(OP_GuildBank, sizeof(GuildBankClear_Struct)); + + GuildBankClear_Struct *gbcs = (GuildBankClear_Struct*) outapp->pBuffer; + + gbcs->Action = GuildBankBulkItems; + gbcs->DepositAreaCount = 0; + gbcs->MainAreaCount = 0; + + FastQueuePacket(&outapp); +} + +void Client::SendGroupCreatePacket() +{ + // For SoD and later clients, this is sent the Group Leader upon initial creation of the group + // + EQApplicationPacket *outapp=new EQApplicationPacket(OP_GroupUpdateB, 32 + strlen(GetName())); + + char *Buffer = (char *)outapp->pBuffer; + // Header + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 1); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // Null Leader name + + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // Member 0 + VARSTRUCT_ENCODE_STRING(Buffer, GetName()); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // This is a string + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, GetLevel()); + VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); + VARSTRUCT_ENCODE_TYPE(uint16, Buffer, 0); + + FastQueuePacket(&outapp); +} + +void Client::SendGroupLeaderChangePacket(const char *LeaderName) +{ + // For SoD and later, send name of Group Leader to this client + + EQApplicationPacket *outapp=new EQApplicationPacket(OP_GroupLeaderChange, sizeof(GroupLeaderChange_Struct)); + + GroupLeaderChange_Struct *glcs = (GroupLeaderChange_Struct*)outapp->pBuffer; + + strn0cpy(glcs->LeaderName, LeaderName, sizeof(glcs->LeaderName)); + + FastQueuePacket(&outapp); +} + +void Client::SendGroupJoinAcknowledge() +{ + // For SoD and later, This produces the 'You have joined the group' message. + EQApplicationPacket* outapp=new EQApplicationPacket(OP_GroupAcknowledge, 4); + FastQueuePacket(&outapp); +} + +void Client::SendAdventureError(const char *error) +{ + size_t error_size = strlen(error); + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AdventureInfo, (error_size + 2)); + strn0cpy((char*)outapp->pBuffer, error, error_size); + FastQueuePacket(&outapp); +} + +void Client::SendAdventureDetails() +{ + if(adv_data) + { + ServerSendAdventureData_Struct *ad = (ServerSendAdventureData_Struct*)adv_data; + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AdventureData, sizeof(AdventureRequestResponse_Struct)); + AdventureRequestResponse_Struct *arr = (AdventureRequestResponse_Struct*)outapp->pBuffer; + arr->unknown000 = 0xBFC40100; + arr->unknown2080 = 0x0A; + arr->risk = ad->risk; + strcpy(arr->text, ad->text); + + if(ad->time_to_enter != 0) + { + arr->timetoenter = ad->time_to_enter; + } + else + { + arr->timeleft = ad->time_left; + } + + if(ad->zone_in_id == zone->GetZoneID()) + { + arr->y = ad->x; + arr->x = ad->y; + arr->showcompass = 1; + } + FastQueuePacket(&outapp); + + SendAdventureCount(ad->count, ad->total); + } + else + { + ServerSendAdventureData_Struct *ad = (ServerSendAdventureData_Struct*)adv_data; + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AdventureData, sizeof(AdventureRequestResponse_Struct)); + FastQueuePacket(&outapp); + } +} + +void Client::SendAdventureCount(uint32 count, uint32 total) +{ + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AdventureUpdate, sizeof(AdventureCountUpdate_Struct)); + AdventureCountUpdate_Struct *acu = (AdventureCountUpdate_Struct*)outapp->pBuffer; + acu->current = count; + acu->total = total; + FastQueuePacket(&outapp); +} + +void Client::NewAdventure(int id, int theme, const char *text, int member_count, const char *members) +{ + size_t text_size = strlen(text); + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AdventureDetails, text_size + 2); + strn0cpy((char*)outapp->pBuffer, text, text_size); + FastQueuePacket(&outapp); + + adv_requested_id = id; + adv_requested_theme = theme; + safe_delete_array(adv_requested_data); + adv_requested_member_count = member_count; + adv_requested_data = new char[64 * member_count]; + memcpy(adv_requested_data, members, (64 * member_count)); +} + +void Client::ClearPendingAdventureData() +{ + adv_requested_id = 0; + adv_requested_theme = 0; + safe_delete_array(adv_requested_data); + adv_requested_member_count = 0; +} + +bool Client::IsOnAdventure() +{ + if(adv_data) + { + ServerSendAdventureData_Struct *ad = (ServerSendAdventureData_Struct*)adv_data; + if(ad->zone_in_id == 0) + { + return false; + } + else + { + return true; + } + } + return false; +} + +void Client::LeaveAdventure() +{ + if(!GetPendingAdventureLeave()) + { + PendingAdventureLeave(); + ServerPacket *pack = new ServerPacket(ServerOP_AdventureLeave, 64); + strcpy((char*)pack->pBuffer, GetName()); + pack->Deflate(); + worldserver.SendPacket(pack); + delete pack; + } +} + +void Client::ClearCurrentAdventure() +{ + if(adv_data) + { + ServerSendAdventureData_Struct* ds = (ServerSendAdventureData_Struct*)adv_data; + if(ds->finished_adventures > 0) + { + ds->instance_id = 0; + ds->risk = 0; + memset(ds->text, 0, 512); + ds->time_left = 0; + ds->time_to_enter = 0; + ds->x = 0; + ds->y = 0; + ds->zone_in_id = 0; + ds->zone_in_object = 0; + } + else + { + safe_delete(adv_data); + } + + SendAdventureError("You are not currently assigned to an adventure."); + } +} + +void Client::AdventureFinish(bool win, int theme, int points) +{ + UpdateLDoNPoints(points, theme); + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AdventureFinish, sizeof(AdventureFinish_Struct)); + AdventureFinish_Struct *af = (AdventureFinish_Struct*)outapp->pBuffer; + af->win_lose = win ? 1 : 0; + af->points = points; + FastQueuePacket(&outapp); +} + +void Client::CheckLDoNHail(Mob *target) +{ + if(!zone->adv_data) + { + return; + } + + if(!target || !target->IsNPC()) + { + return; + } + + if(target->GetOwnerID() != 0) + { + return; + } + + ServerZoneAdventureDataReply_Struct* ds = (ServerZoneAdventureDataReply_Struct*)zone->adv_data; + if(ds->type != Adventure_Rescue) + { + return; + } + + if(ds->data_id != target->GetNPCTypeID()) + { + return; + } + + if(entity_list.CheckNPCsClose(target) != 0) + { + target->Say("You're here to save me? I couldn't possibly risk leaving yet. There are " + "far too many of those horrid things out there waiting to recapture me! Please get" + " rid of some more of those vermin and then we can try to leave."); + return; + } + + Mob *pet = GetPet(); + if(pet) + { + if(pet->GetPetType() == petCharmed) + { + pet->BuffFadeByEffect(SE_Charm); + } + else if(pet->GetPetType() == petNPCFollow) + { + pet->SetOwnerID(0); + } + else + { + pet->Depop(); + } + } + + SetPet(target); + target->SetOwnerID(GetID()); + target->Say("Wonderful! Someone to set me free! I feared for my life for so long," + " never knowing when they might choose to end my life. Now that you're here though" + " I can rest easy. Please help me find my way out of here as soon as you can" + " I'll stay close behind you!"); +} + +void Client::CheckEmoteHail(Mob *target, const char* message) +{ + if( + (message[0] != 'H' && + message[0] != 'h') || + message[1] != 'a' || + message[2] != 'i' || + message[3] != 'l'){ + return; + } + + if(!target || !target->IsNPC()) + { + return; + } + + if(target->GetOwnerID() != 0) + { + return; + } + uint16 emoteid = target->GetEmoteID(); + if(emoteid != 0) + target->CastToNPC()->DoNPCEmote(HAILED,emoteid); +} + +void Client::MarkSingleCompassLoc(float in_x, float in_y, float in_z, uint8 count) +{ + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_DzCompass, sizeof(ExpeditionInfo_Struct) + sizeof(ExpeditionCompassEntry_Struct) * count); + ExpeditionCompass_Struct *ecs = (ExpeditionCompass_Struct*)outapp->pBuffer; + //ecs->clientid = GetID(); + ecs->count = count; + + if (count) { + ecs->entries[0].x = in_x; + ecs->entries[0].y = in_y; + ecs->entries[0].z = in_z; + } + + FastQueuePacket(&outapp); + safe_delete(outapp); +} + +void Client::SendZonePoints() +{ + int count = 0; + LinkedListIterator iterator(zone->zone_point_list); + iterator.Reset(); + while(iterator.MoreElements()) + { + ZonePoint* data = iterator.GetData(); + if(GetClientVersionBit() & data->client_version_mask) + { + count++; + } + iterator.Advance(); + } + + uint32 zpsize = sizeof(ZonePoints) + ((count + 1) * sizeof(ZonePoint_Entry)); + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SendZonepoints, zpsize); + ZonePoints* zp = (ZonePoints*)outapp->pBuffer; + zp->count = count; + + int i = 0; + iterator.Reset(); + while(iterator.MoreElements()) + { + ZonePoint* data = iterator.GetData(); + if(GetClientVersionBit() & data->client_version_mask) + { + zp->zpe[i].iterator = data->number; + zp->zpe[i].x = data->target_x; + zp->zpe[i].y = data->target_y; + zp->zpe[i].z = data->target_z; + zp->zpe[i].heading = data->target_heading; + zp->zpe[i].zoneid = data->target_zone_id; + zp->zpe[i].zoneinstance = data->target_zone_instance; + i++; + } + iterator.Advance(); + } + FastQueuePacket(&outapp); +} + +void Client::SendTargetCommand(uint32 EntityID) +{ + EQApplicationPacket* outapp = new EQApplicationPacket(OP_TargetCommand, sizeof(ClientTarget_Struct)); + ClientTarget_Struct *cts = (ClientTarget_Struct*)outapp->pBuffer; + cts->new_target = EntityID; + FastQueuePacket(&outapp); +} + +void Client::LocateCorpse() +{ + Corpse *ClosestCorpse = nullptr; + if(!GetTarget()) + ClosestCorpse = entity_list.GetClosestCorpse(this, nullptr); + else if(GetTarget()->IsCorpse()) + ClosestCorpse = entity_list.GetClosestCorpse(this, GetTarget()->CastToCorpse()->GetOwnerName()); + else + ClosestCorpse = entity_list.GetClosestCorpse(this, GetTarget()->GetCleanName()); + + if(ClosestCorpse) + { + Message_StringID(MT_Spells, SENSE_CORPSE_DIRECTION); + SetHeading(CalculateHeadingToTarget(ClosestCorpse->GetX(), ClosestCorpse->GetY())); + SetTarget(ClosestCorpse); + SendTargetCommand(ClosestCorpse->GetID()); + SendPosUpdate(2); + } + else if(!GetTarget()) + Message_StringID(clientMessageError, SENSE_CORPSE_NONE); + else + Message_StringID(clientMessageError, SENSE_CORPSE_NOT_NAME); +} + +void Client::NPCSpawn(NPC *target_npc, const char *identifier, uint32 extra) +{ + if (!target_npc || !identifier) + return; + + std::string id = identifier; + for(int i = 0; i < id.length(); ++i) + { + id[i] = tolower(id[i]); + } + + if (id == "create") { + // extra tries to create the npc_type ID within the range for the current zone (zone_id * 1000) + database.NPCSpawnDB(0, zone->GetShortName(), zone->GetInstanceVersion(), this, target_npc->CastToNPC(), extra); + } + else if (id == "add") { + // extra sets the respawn timer for add + database.NPCSpawnDB(1, zone->GetShortName(), zone->GetInstanceVersion(), this, target_npc->CastToNPC(), extra); + } + else if (id == "update") { + database.NPCSpawnDB(2, zone->GetShortName(), zone->GetInstanceVersion(), this, target_npc->CastToNPC()); + } + else if (id == "remove") { + database.NPCSpawnDB(3, zone->GetShortName(), zone->GetInstanceVersion(), this, target_npc->CastToNPC()); + target_npc->Depop(false); + } + else if (id == "delete") { + database.NPCSpawnDB(4, zone->GetShortName(), zone->GetInstanceVersion(), this, target_npc->CastToNPC()); + target_npc->Depop(false); + } + else { + return; + } +} + +bool Client::IsDraggingCorpse(uint16 CorpseID) +{ + for (auto It = DraggedCorpses.begin(); It != DraggedCorpses.end(); ++It) { + if (It->second == CorpseID) + return true; + } + + return false; +} + +void Client::DragCorpses() +{ + for (auto It = DraggedCorpses.begin(); It != DraggedCorpses.end(); ++It) { + Mob *corpse = entity_list.GetMob(It->second); + + if (corpse && corpse->IsPlayerCorpse() && + (DistNoRootNoZ(*corpse) <= RuleR(Character, DragCorpseDistance))) + continue; + + if (!corpse || !corpse->IsPlayerCorpse() || + corpse->CastToCorpse()->IsBeingLooted() || + !corpse->CastToCorpse()->Summon(this, false, false)) { + Message_StringID(MT_DefaultText, CORPSEDRAG_STOP); + It = DraggedCorpses.erase(It); + } + } +} + +void Client::Doppelganger(uint16 spell_id, Mob *target, const char *name_override, int pet_count, int pet_duration) +{ + if(!target || !IsValidSpell(spell_id) || this->GetID() == target->GetID()) + return; + + PetRecord record; + if(!database.GetPetEntry(spells[spell_id].teleport_zone, &record)) + { + LogFile->write(EQEMuLog::Error, "Unknown doppelganger spell id: %d, check pets table", spell_id); + Message(13, "Unable to find data for pet %s", spells[spell_id].teleport_zone); + return; + } + + AA_SwarmPet pet; + pet.count = pet_count; + pet.duration = pet_duration; + pet.npc_id = record.npc_type; + + NPCType *made_npc = nullptr; + + const NPCType *npc_type = database.GetNPCType(pet.npc_id); + if(npc_type == nullptr) { + LogFile->write(EQEMuLog::Error, "Unknown npc type for doppelganger spell id: %d", spell_id); + Message(0,"Unable to find pet!"); + return; + } + // make a custom NPC type for this + made_npc = new NPCType; + memcpy(made_npc, npc_type, sizeof(NPCType)); + + strcpy(made_npc->name, name_override); + made_npc->level = GetLevel(); + made_npc->race = GetRace(); + made_npc->gender = GetGender(); + made_npc->size = GetSize(); + made_npc->AC = GetAC(); + made_npc->STR = GetSTR(); + made_npc->STA = GetSTA(); + made_npc->DEX = GetDEX(); + made_npc->AGI = GetAGI(); + made_npc->MR = GetMR(); + made_npc->FR = GetFR(); + made_npc->CR = GetCR(); + made_npc->DR = GetDR(); + made_npc->PR = GetPR(); + made_npc->Corrup = GetCorrup(); + // looks + made_npc->texture = GetEquipmentMaterial(MaterialChest); + made_npc->helmtexture = GetEquipmentMaterial(MaterialHead); + made_npc->haircolor = GetHairColor(); + made_npc->beardcolor = GetBeardColor(); + made_npc->eyecolor1 = GetEyeColor1(); + made_npc->eyecolor2 = GetEyeColor2(); + made_npc->hairstyle = GetHairStyle(); + made_npc->luclinface = GetLuclinFace(); + made_npc->beard = GetBeard(); + made_npc->drakkin_heritage = GetDrakkinHeritage(); + made_npc->drakkin_tattoo = GetDrakkinTattoo(); + made_npc->drakkin_details = GetDrakkinDetails(); + made_npc->d_meele_texture1 = GetEquipmentMaterial(MaterialPrimary); + made_npc->d_meele_texture2 = GetEquipmentMaterial(MaterialSecondary); + for (int i = EmuConstants::MATERIAL_BEGIN; i <= EmuConstants::MATERIAL_END; i++) { + made_npc->armor_tint[i] = GetEquipmentColor(i); + } + made_npc->loottable_id = 0; + + npc_type = made_npc; + + int summon_count = 0; + summon_count = pet.count; + + if(summon_count > MAX_SWARM_PETS) + summon_count = MAX_SWARM_PETS; + + static const float swarm_pet_x[MAX_SWARM_PETS] = { 5, -5, 5, -5, 10, -10, 10, -10, 8, -8, 8, -8 }; + static const float swarm_pet_y[MAX_SWARM_PETS] = { 5, 5, -5, -5, 10, 10, -10, -10, 8, 8, -8, -8 }; + TempPets(true); + + while(summon_count > 0) { + NPCType *npc_dup = nullptr; + if(made_npc != nullptr) { + npc_dup = new NPCType; + memcpy(npc_dup, made_npc, sizeof(NPCType)); + } + + NPC* npca = new NPC( + (npc_dup!=nullptr)?npc_dup:npc_type, //make sure we give the NPC the correct data pointer + 0, + GetX()+swarm_pet_x[summon_count], GetY()+swarm_pet_y[summon_count], + GetZ(), GetHeading(), FlyMode3); + + if(!npca->GetSwarmInfo()){ + AA_SwarmPetInfo* nSI = new AA_SwarmPetInfo; + npca->SetSwarmInfo(nSI); + npca->GetSwarmInfo()->duration = new Timer(pet_duration*1000); + } + else{ + npca->GetSwarmInfo()->duration->Start(pet_duration*1000); + } + + npca->GetSwarmInfo()->owner_id = GetID(); + + // Give the pets alittle more agro than the caster and then agro them on the target + target->AddToHateList(npca, (target->GetHateAmount(this) + 100), (target->GetDamageAmount(this) + 100)); + npca->AddToHateList(target, 1000, 1000); + npca->GetSwarmInfo()->target = target->GetID(); + + //we allocated a new NPC type object, give the NPC ownership of that memory + if(npc_dup != nullptr) + npca->GiveNPCTypeData(npc_dup); + + entity_list.AddNPC(npca); + summon_count--; + } +} + +void Client::AssignToInstance(uint16 instance_id) +{ + database.AddClientToInstance(instance_id, CharacterID()); +} + +void Client::SendStatsWindow(Client* client, bool use_window) +{ + // Define the types of page breaks we need + std::string indP = " "; + std::string indS = "          "; + std::string indM = "                          "; + std::string indL = "                                 "; + std::string div = " | "; + + std::string color_red = ""; + std::string color_blue = ""; + std::string color_green = ""; + std::string bright_green = ""; + std::string bright_red = ""; + std::string heroic_color = " +"; + + // Set Class + std::string class_Name = itoa(GetClass()); + std::string class_List[] = { "WAR", "CLR", "PAL", "RNG", "SK", "DRU", "MNK", "BRD", "ROG", "SHM", "NEC", "WIZ", "MAG", "ENC", "BST", "BER" }; + + if(GetClass() < 17 && GetClass() > 0) { class_Name = class_List[GetClass()-1]; } + + // Race + std::string race_Name = itoa(GetRace()); + switch(GetRace()) + { + case 1: race_Name = "Human"; break; + case 2: race_Name = "Barbarian"; break; + case 3: race_Name = "Erudite"; break; + case 4: race_Name = "Wood Elf"; break; + case 5: race_Name = "High Elf"; break; + case 6: race_Name = "Dark Elf"; break; + case 7: race_Name = "Half Elf"; break; + case 8: race_Name = "Dwarf"; break; + case 9: race_Name = "Troll"; break; + case 10: race_Name = "Ogre"; break; + case 11: race_Name = "Halfing"; break; + case 12: race_Name = "Gnome"; break; + case 128: race_Name = "Iksar"; break; + case 130: race_Name = "Vah Shir"; break; + case 330: race_Name = "Froglok"; break; + case 522: race_Name = "Drakkin"; break; + default: break; + } + /*########################################################## + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + H/M/E String + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ##########################################################*/ + std::string HME_row = ""; + //Loop Variables + /*===========================*/ + std::string cur_field = ""; + std::string total_field = ""; + std::string cur_name = ""; + std::string cur_spacing = ""; + std::string cur_color = ""; + + int hme_rows = 3; // Rows in display + int max_HME_value_len = 9; // 9 digits in the displayed value + + for(int hme_row_counter = 0; hme_row_counter < hme_rows; hme_row_counter++) + { + switch(hme_row_counter) { + case 0: { + cur_name = " H: "; + cur_field = itoa(GetHP()); + total_field = itoa(GetMaxHP()); + break; + } + case 1: { + if(CalcMaxMana() > 0) { + cur_name = " M: "; + cur_field = itoa(GetMana()); + total_field = itoa(CalcMaxMana()); + } + else { continue; } + + break; + } + case 2: { + cur_name = " E: "; + cur_field = itoa(GetEndurance()); + total_field = itoa(GetMaxEndurance()); + break; + } + default: { break; } + } + if(cur_field.compare(total_field) == 0) { cur_color = bright_green; } + else { cur_color = bright_red; } + + cur_spacing.clear(); + for(int a = cur_field.size(); a < max_HME_value_len; a++) { cur_spacing += " ."; } + + HME_row += indM + cur_name + cur_spacing + cur_color + cur_field + " / " + total_field + "
"; + } + /*########################################################## + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Regen String + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ##########################################################*/ + std::string regen_string; + //Loop Variables + /*===========================*/ + std::string regen_row_header = ""; + std::string regen_row_color = ""; + std::string base_regen_field = ""; + std::string base_regen_spacing = ""; + std::string item_regen_field = ""; + std::string item_regen_spacing = ""; + std::string cap_regen_field = ""; + std::string cap_regen_spacing = ""; + std::string spell_regen_field = ""; + std::string spell_regen_spacing = ""; + std::string aa_regen_field = ""; + std::string aa_regen_spacing = ""; + std::string total_regen_field = ""; + int regen_rows = 3; // Number of rows + int max_regen_value_len = 5; // 5 digits in the displayed value(larger values will not get cut off, this is just a baseline) + + for(int regen_row_counter = 0; regen_row_counter < regen_rows; regen_row_counter++) + { + switch(regen_row_counter) + { + case 0: { + regen_row_header = "H: "; + regen_row_color = color_red; + + base_regen_field = itoa(LevelRegen()); + item_regen_field = itoa(itembonuses.HPRegen); + cap_regen_field = itoa(CalcHPRegenCap()); + spell_regen_field = itoa(spellbonuses.HPRegen); + aa_regen_field = itoa(aabonuses.HPRegen); + total_regen_field = itoa(CalcHPRegen()); + break; + } + case 1: { + if(CalcMaxMana() > 0) { + regen_row_header = "M: "; + regen_row_color = color_blue; + + base_regen_field = itoa(CalcBaseManaRegen()); + item_regen_field = itoa(itembonuses.ManaRegen); + cap_regen_field = itoa(CalcManaRegenCap()); + spell_regen_field = itoa(spellbonuses.ManaRegen); + aa_regen_field = itoa(aabonuses.ManaRegen); + total_regen_field = itoa(CalcManaRegen()); + } + else { continue; } + break; + } + case 2: { + regen_row_header = "E: "; + regen_row_color = color_green; + + base_regen_field = itoa(((GetLevel() * 4 / 10) + 2)); + item_regen_field = itoa(itembonuses.EnduranceRegen); + cap_regen_field = itoa(CalcEnduranceRegenCap()); + spell_regen_field = itoa(spellbonuses.EnduranceRegen); + aa_regen_field = itoa(aabonuses.EnduranceRegen); + total_regen_field = itoa(CalcEnduranceRegen()); + break; + } + default: { break; } + } + + base_regen_spacing.clear(); + item_regen_spacing.clear(); + cap_regen_spacing.clear(); + spell_regen_spacing.clear(); + aa_regen_spacing.clear(); + + for(int b = base_regen_field.size(); b < max_regen_value_len; b++) { base_regen_spacing += " ."; } + for(int b = item_regen_field.size(); b < max_regen_value_len; b++) { item_regen_spacing += " ."; } + for(int b = cap_regen_field.size(); b < max_regen_value_len; b++) { cap_regen_spacing += " ."; } + for(int b = spell_regen_field.size(); b < max_regen_value_len; b++) { spell_regen_spacing += " ."; } + for(int b = aa_regen_field.size(); b < max_regen_value_len; b++) { aa_regen_spacing += " ."; } + + regen_string += indS + regen_row_color + regen_row_header + base_regen_spacing + base_regen_field; + regen_string += div + item_regen_spacing + item_regen_field + " (" + cap_regen_field; + regen_string += ") " + cap_regen_spacing + div + spell_regen_spacing + spell_regen_field; + regen_string += div + aa_regen_spacing + aa_regen_field + div + total_regen_field + "

"; + } + /*########################################################## + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Stat String + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ##########################################################*/ + std::string stat_field = ""; + //Loop Variables + /*===========================*/ + //first field(stat) + std::string a_stat = "";; + std::string a_stat_name = ""; + std::string a_stat_spacing = ""; + //second field(heroic stat) + std::string h_stat = ""; + std::string h_stat_spacing = ""; + //third field(resist) + std::string a_resist = ""; + std::string a_resist_name = ""; + std::string a_resist_spacing = ""; + //fourth field(heroic resist) + std::string h_resist_field = ""; + + int stat_rows = 7; // Number of rows + int max_stat_value_len = 3; // 3 digits in the displayed value + + for(int stat_row_counter = 0; stat_row_counter < stat_rows; stat_row_counter++) + { + switch(stat_row_counter) { + case 0: { + a_stat_name = " STR: "; + a_resist_name = "MR: "; + a_stat = itoa(GetSTR()); + h_stat = itoa(GetHeroicSTR()); + a_resist = itoa(GetMR()); + h_resist_field = itoa(GetHeroicMR()); + break; + } + case 1: { + a_stat_name = " STA: "; + a_resist_name = "CR: "; + a_stat = itoa(GetSTA()); + h_stat = itoa(GetHeroicSTA()); + a_resist = itoa(GetCR()); + h_resist_field = itoa(GetHeroicCR()); + break; + } + case 2: { + a_stat_name = " AGI : "; + a_resist_name = "FR: "; + a_stat = itoa(GetAGI()); + h_stat = itoa(GetHeroicAGI()); + a_resist = itoa(GetFR()); + h_resist_field = itoa(GetHeroicFR()); + break; + } + case 3: { + a_stat_name = " DEX: "; + a_resist_name = "PR: "; + a_stat = itoa(GetDEX()); + h_stat = itoa(GetHeroicDEX()); + a_resist = itoa(GetPR()); + h_resist_field = itoa(GetHeroicPR()); + break; + } + case 4: { + a_stat_name = " INT : "; + a_resist_name = "DR: "; + a_stat = itoa(GetINT()); + h_stat = itoa(GetHeroicINT()); + a_resist = itoa(GetDR()); + h_resist_field = itoa(GetHeroicDR()); + break; + } + case 5: { + a_stat_name = " WIS: "; + a_resist_name = "Cp: "; + a_stat = itoa(GetWIS()); + h_stat = itoa(GetHeroicWIS()); + a_resist = itoa(GetCorrup()); + h_resist_field = itoa(GetHeroicCorrup()); + break; + } + case 6: { + a_stat_name = " CHA: "; + a_stat = itoa(GetCHA()); + h_stat = itoa(GetHeroicCHA()); + break; + } + default: { break; } + } + + a_stat_spacing.clear(); + h_stat_spacing.clear(); + a_resist_spacing.clear(); + + for(int a = a_stat.size(); a < max_stat_value_len; a++) { a_stat_spacing += " . "; } + for(int h = h_stat.size(); h < 20; h++) { h_stat_spacing += " . "; } + for(int h = a_resist.size(); h < max_stat_value_len; h++) { a_resist_spacing += " . "; } + + stat_field += indP + a_stat_name + a_stat_spacing + a_stat + heroic_color + h_stat + "
"; + if(stat_row_counter < 6) { + stat_field += h_stat_spacing + a_resist_name + a_resist_spacing + a_resist + heroic_color + h_resist_field + "

"; + } + } + /*########################################################## + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Mod2 String + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ##########################################################*/ + std::string mod2_field = ""; + //Loop Variables + /*===========================*/ + std::string mod2a = ""; + std::string mod2a_name = ""; + std::string mod2a_spacing = ""; + std::string mod2a_cap = ""; + std::string mod_row_spacing = ""; + std::string mod2b = ""; + std::string mod2b_name = ""; + std::string mod2b_spacing = ""; + std::string mod2b_cap = ""; + int mod2a_space_count; + int mod2b_space_count; + + int mod2_rows = 4; + int max_mod2_value_len = 3; // 3 digits in the displayed value + + for(int mod2_row_counter = 0; mod2_row_counter < mod2_rows; mod2_row_counter++) + { + switch (mod2_row_counter) + { + case 0: { + mod2a_name = "Avoidance: "; + mod2b_name = "Combat Effects: "; + mod2a = itoa(GetAvoidance()); + mod2a_cap = itoa(RuleI(Character, ItemAvoidanceCap)); + mod2b = itoa(GetCombatEffects()); + mod2b_cap = itoa(RuleI(Character, ItemCombatEffectsCap)); + mod2a_space_count = 2; + mod2b_space_count = 0; + break; + } + case 1: { + mod2a_name = "Accuracy: "; + mod2b_name = "Strike Through: "; + mod2a = itoa(GetAccuracy()); + mod2a_cap = itoa(RuleI(Character, ItemAccuracyCap)); + mod2b = itoa(GetStrikeThrough()); + mod2b_cap = itoa(RuleI(Character, ItemStrikethroughCap)); + mod2a_space_count = 3; + mod2b_space_count = 1; + break; + } + case 2: { + mod2a_name = "Shielding: "; + mod2b_name = "Spell Shielding: "; + mod2a = itoa(GetShielding()); + mod2a_cap = itoa(RuleI(Character, ItemShieldingCap)); + mod2b = itoa(GetSpellShield()); + mod2b_cap = itoa(RuleI(Character, ItemSpellShieldingCap)); + mod2a_space_count = 2; + mod2b_space_count = 1; + break; + } + case 3: { + mod2a_name = "Stun Resist: "; + mod2b_name = "DoT Shielding: "; + mod2a = itoa(GetStunResist()); + mod2a_cap = itoa(RuleI(Character, ItemStunResistCap)); + mod2b = itoa(GetDoTShield()); + mod2b_cap = itoa(RuleI(Character, ItemDoTShieldingCap)); + mod2a_space_count = 0; + mod2b_space_count = 2; + break; + } + } + + mod2a_spacing.clear(); + mod_row_spacing.clear(); + mod2b_spacing.clear(); + + for(int a = mod2a.size(); a < (max_mod2_value_len + mod2a_space_count); a++) { mod2a_spacing += " . "; } + for(int a = mod2a_cap.size(); a < 6 ; a++) { mod_row_spacing += " . "; } + for(int a = mod2b.size(); a < (max_mod2_value_len + mod2b_space_count); a++) { mod2b_spacing += " . "; } + + mod2_field += indP + mod2a_name + mod2a_spacing + mod2a + " / " + mod2a_cap + mod_row_spacing; + mod2_field += mod2b_name + mod2b_spacing + mod2b + " / " + mod2b_cap + "
"; + } + + uint32 rune_number = 0; + uint32 magic_rune_number = 0; + uint32 buff_count = GetMaxTotalSlots(); + for (int i=0; i < buff_count; i++) { + if (buffs[i].spellid != SPELL_UNKNOWN) { + if (buffs[i].melee_rune > 0) { rune_number += buffs[i].melee_rune; } + + if (buffs[i].magic_rune > 0) { magic_rune_number += buffs[i].magic_rune; } + } + } + + int shield_ac = 0; + GetRawACNoShield(shield_ac); + + std::string skill_list[] = { + "1H Blunt","1H Slashing","2H Blunt","2H Slashing","Abjuration","Alteration","Apply Poison","Archery","Backstab","Bind Wound","Bash","Block","Brass Instruments","Channeling","Conjuration", + "Defense","Disarm","Disarm Traps","Divination","Dodge","Double Attack","Dragon Punch","Dual Wield","Eagle Strike","Evocation","Feign Death","Flying Kick","Forage","Hand To Hand","Hide","Kick", + "Meditate","Mend","Offense","Parry","Pick Lock","Piercing","Riposte","Round Kick","Safe Fall","Sense Heading","Singing","Sneak","Specialize Abjuration","Specialize Alteration","Specialize Conjuration", + "Specialize Divination","Specialize Evocation","Pick Pockets","Stringed_Instruments","Swimming","Throwing","Tiger Claw","Tracking","Wind Instruments","Fishing","Make Poison","Tinkering","Research","Alchemy", + "Baking","Tailoring","Sense Traps","Blacksmithing","Fletching","Brewing","Alcohol_Tolerance","Begging","Jewelry Making","Pottery","Percussion Instruments","Intimidation","Berserking","Taunt","Frenzy" + }; + + std::string skill_mods = ""; + for(int j = 0; j <= HIGHEST_SKILL; j++) { + if(itembonuses.skillmod[j] > 0) + skill_mods += indP + skill_list[j] + " : +" + itoa(itembonuses.skillmod[j]) + "%
"; + else if(itembonuses.skillmod[j] < 0) + skill_mods += indP + skill_list[j] + " : -" + itoa(itembonuses.skillmod[j]) + "%
"; + } + + std::string skill_dmgs = ""; + for(int j = 0; j <= HIGHEST_SKILL; j++) { + if((itembonuses.SkillDamageAmount[j] + spellbonuses.SkillDamageAmount[j]) > 0) + skill_dmgs += indP + skill_list[j] + " : +" + itoa(itembonuses.SkillDamageAmount[j] + spellbonuses.SkillDamageAmount[j]) + "
"; + else if((itembonuses.SkillDamageAmount[j] + spellbonuses.SkillDamageAmount[j]) < 0) + skill_dmgs += indP + skill_list[j] + " : -" + itoa(itembonuses.SkillDamageAmount[j] + spellbonuses.SkillDamageAmount[j]) + "
"; + } + + std::string faction_item_string = ""; + char faction_buf[256]; + + for(std::map ::iterator iter = item_faction_bonuses.begin(); + iter != item_faction_bonuses.end(); + ++iter) + { + memset(&faction_buf, 0, sizeof(faction_buf)); + + if(!database.GetFactionName((int32)((*iter).first), faction_buf, sizeof(faction_buf))) + strcpy(faction_buf, "Not in DB"); + + if((*iter).second > 0) { + faction_item_string += indP + faction_buf + " : +" + itoa((*iter).second) + "
"; + } + else if((*iter).second < 0) { + faction_item_string += indP + faction_buf + " : -" + itoa((*iter).second) + "
"; + } + } + + std::string bard_info = ""; + if(GetClass() == BARD) { + bard_info = indP + "Singing: " + itoa(GetSingMod()) + "
" + + indP + "Brass: " + itoa(GetBrassMod()) + "
" + + indP + "String: " + itoa(GetStringMod()) + "
" + + indP + "Percussion: " + itoa(GetPercMod()) + "
" + + indP + "Wind: " + itoa(GetWindMod()) + "
"; + } + + std::ostringstream final_string; + final_string << + /* C/L/R */ indP << "Class: " << class_Name << indS << "Level: " << static_cast(GetLevel()) << indS << "Race: " << race_Name << "
" << + /* Runes */ indP << "Rune: " << rune_number << indL << indS << "Spell Rune: " << magic_rune_number << "
" << + /* HP/M/E */ HME_row << + /* DS */ indP << "DS: " << (itembonuses.DamageShield + spellbonuses.DamageShield*-1) << " (Spell: " << (spellbonuses.DamageShield*-1) << " + Item: " << itembonuses.DamageShield << " / " << RuleI(Character, ItemDamageShieldCap) << ")
" << + /* Atk */ indP << "ATK: " << GetTotalATK() << "
" << + /* Atk2 */ indP << "- Base: " << GetATKRating() << " | Item: " << itembonuses.ATK << " (" << RuleI(Character, ItemATKCap) << ")~Used: " << (itembonuses.ATK * 1.342) << " | Spell: " << spellbonuses.ATK << "
" << + /* AC */ indP << "AC: " << CalcAC() << "
" << + /* AC2 */ indP << "- Mit: " << GetACMit() << " | Avoid: " << GetACAvoid() << " | Spell: " << spellbonuses.AC << " | Shield: " << shield_ac << "
" << + /* Haste */ indP << "Haste: " << GetHaste() << "
" << + /* Haste2 */ indP << " - Item: " << itembonuses.haste << " + Spell: " << (spellbonuses.haste + spellbonuses.hastetype2) << " (Cap: " << RuleI(Character, HasteCap) << ") | Over: " << (spellbonuses.hastetype3 + ExtraHaste) << "

" << + /* RegenLbl */ indL << indS << "Regen
" << indS << indP << indP << " Base | Items (Cap) " << indP << " | Spell | A.A.s | Total
" << + /* Regen */ regen_string << "
" << + /* Stats */ stat_field << "

" << + /* Mod2s */ mod2_field << "
" << + /* HealAmt */ indP << "Heal Amount: " << GetHealAmt() << " / " << RuleI(Character, ItemHealAmtCap) << "
" << + /* SpellDmg*/ indP << "Spell Dmg: " << GetSpellDmg() << " / " << RuleI(Character, ItemSpellDmgCap) << "
" << + /* Clair */ indP << "Clairvoyance: " << GetClair() << " / " << RuleI(Character, ItemClairvoyanceCap) << "
" << + /* DSMit */ indP << "Dmg Shld Mit: " << GetDSMit() << " / " << RuleI(Character, ItemDSMitigationCap) << "

"; + if(GetClass() == BARD) + final_string << bard_info << "
"; + if(skill_mods.size() > 0) + final_string << skill_mods << "
"; + if(skill_dmgs.size() > 0) + final_string << skill_dmgs << "
"; + if(faction_item_string.size() > 0) + final_string << faction_item_string; + + std::string final_stats = final_string.str(); + + if(use_window) { + if(final_stats.size() < 4096) + { + uint32 Buttons = (client->GetClientVersion() < EQClientSoD) ? 0 : 1; + client->SendWindow(0, POPUPID_UPDATE_SHOWSTATSWINDOW, Buttons, "Cancel", "Update", 0, 1, this, "", "%s", final_stats.c_str()); + goto Extra_Info; + } + else { + client->Message(15, "The window has exceeded its character limit, displaying stats to chat window:"); + } + } + + client->Message(15, "~~~~~ %s %s ~~~~~", GetCleanName(), GetLastName()); + client->Message(0, " Level: %i Class: %i Race: %i DS: %i/%i Size: %1.1f Weight: %.1f/%d ", GetLevel(), GetClass(), GetRace(), GetDS(), RuleI(Character, ItemDamageShieldCap), GetSize(), (float)CalcCurrentWeight() / 10.0f, GetSTR()); + client->Message(0, " HP: %i/%i HP Regen: %i/%i",GetHP(), GetMaxHP(), CalcHPRegen(), CalcHPRegenCap()); + client->Message(0, " AC: %i ( Mit.: %i + Avoid.: %i + Spell: %i ) | Shield AC: %i", CalcAC(), GetACMit(), GetACAvoid(), spellbonuses.AC, shield_ac); + if(CalcMaxMana() > 0) + client->Message(0, " Mana: %i/%i Mana Regen: %i/%i", GetMana(), GetMaxMana(), CalcManaRegen(), CalcManaRegenCap()); + client->Message(0, " End.: %i/%i End. Regen: %i/%i",GetEndurance(), GetMaxEndurance(), CalcEnduranceRegen(), CalcEnduranceRegenCap()); + client->Message(0, " ATK: %i Worn/Spell ATK %i/%i Server Side ATK: %i", GetTotalATK(), RuleI(Character, ItemATKCap), GetATKBonus(), GetATK()); + client->Message(0, " Haste: %i / %i (Item: %i + Spell: %i + Over: %i)", GetHaste(), RuleI(Character, HasteCap), itembonuses.haste, spellbonuses.haste + spellbonuses.hastetype2, spellbonuses.hastetype3 + ExtraHaste); + client->Message(0, " STR: %i STA: %i DEX: %i AGI: %i INT: %i WIS: %i CHA: %i", GetSTR(), GetSTA(), GetDEX(), GetAGI(), GetINT(), GetWIS(), GetCHA()); + client->Message(0, " hSTR: %i hSTA: %i hDEX: %i hAGI: %i hINT: %i hWIS: %i hCHA: %i", GetHeroicSTR(), GetHeroicSTA(), GetHeroicDEX(), GetHeroicAGI(), GetHeroicINT(), GetHeroicWIS(), GetHeroicCHA()); + client->Message(0, " MR: %i PR: %i FR: %i CR: %i DR: %i Corruption: %i", GetMR(), GetPR(), GetFR(), GetCR(), GetDR(), GetCorrup()); + client->Message(0, " hMR: %i hPR: %i hFR: %i hCR: %i hDR: %i hCorruption: %i", GetHeroicMR(), GetHeroicPR(), GetHeroicFR(), GetHeroicCR(), GetHeroicDR(), GetHeroicCorrup()); + client->Message(0, " Shielding: %i Spell Shield: %i DoT Shielding: %i Stun Resist: %i Strikethrough: %i Avoidance: %i Accuracy: %i Combat Effects: %i", GetShielding(), GetSpellShield(), GetDoTShield(), GetStunResist(), GetStrikeThrough(), GetAvoidance(), GetAccuracy(), GetCombatEffects()); + client->Message(0, " Heal Amt.: %i Spell Dmg.: %i Clairvoyance: %i DS Mitigation: %i", GetHealAmt(), GetSpellDmg(), GetClair(), GetDSMit()); + if(GetClass() == BARD) + client->Message(0, " Singing: %i Brass: %i String: %i Percussion: %i Wind: %i", GetSingMod(), GetBrassMod(), GetStringMod(), GetPercMod(), GetWindMod()); + + Extra_Info: + + client->Message(0, " BaseRace: %i Gender: %i BaseGender: %i Texture: %i HelmTexture: %i", GetBaseRace(), GetGender(), GetBaseGender(), GetTexture(), GetHelmTexture()); + if (client->Admin() >= 100) { + client->Message(0, " CharID: %i EntityID: %i PetID: %i OwnerID: %i AIControlled: %i Targetted: %i", CharacterID(), GetID(), GetPetID(), GetOwnerID(), IsAIControlled(), targeted); + } +} + +void Client::SendAltCurrencies() { + if(GetClientVersion() >= EQClientSoF) { + uint32 count = zone->AlternateCurrencies.size(); + if(count == 0) { + return; + } + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_AltCurrency, + sizeof(AltCurrencyPopulate_Struct) + sizeof(AltCurrencyPopulateEntry_Struct) * count); + AltCurrencyPopulate_Struct *altc = (AltCurrencyPopulate_Struct*)outapp->pBuffer; + altc->opcode = ALT_CURRENCY_OP_POPULATE; + altc->count = count; + + uint32 i = 0; + std::list::iterator iter = zone->AlternateCurrencies.begin(); + while(iter != zone->AlternateCurrencies.end()) { + const Item_Struct* item = database.GetItem((*iter).item_id); + altc->entries[i].currency_number = (*iter).id; + altc->entries[i].unknown00 = 1; + altc->entries[i].currency_number2 = (*iter).id; + altc->entries[i].item_id = (*iter).item_id; + if(item) { + altc->entries[i].item_icon = item->Icon; + altc->entries[i].stack_size = item->StackSize; + } else { + altc->entries[i].item_icon = 1000; + altc->entries[i].stack_size = 1000; + } + i++; + ++iter; + } + + FastQueuePacket(&outapp); + } +} + +void Client::SetAlternateCurrencyValue(uint32 currency_id, uint32 new_amount) +{ + alternate_currency[currency_id] = new_amount; + database.UpdateAltCurrencyValue(CharacterID(), currency_id, new_amount); + SendAlternateCurrencyValue(currency_id); +} + +void Client::AddAlternateCurrencyValue(uint32 currency_id, int32 amount, int8 method) +{ + + /* Added via Quest, rest of the logging methods may be done inline due to information available in that area of the code */ + if (method == 1){ + /* QS: PlayerLogAlternateCurrencyTransactions :: Cursor to Item Storage */ + if (RuleB(QueryServ, PlayerLogAlternateCurrencyTransactions)){ + std::string event_desc = StringFormat("Added via Quest :: Cursor to Item :: alt_currency_id:%i amount:%i in zoneid:%i instid:%i", currency_id, this->GetZoneID(), this->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_Alternate_Currency_Transactions, this->CharacterID(), event_desc); + } + } + + if(amount == 0) { + return; + } + + if(!alternate_currency_loaded) { + alternate_currency_queued_operations.push(std::make_pair(currency_id, amount)); + return; + } + + int new_value = 0; + std::map::iterator iter = alternate_currency.find(currency_id); + if(iter == alternate_currency.end()) { + new_value = amount; + } else { + new_value = (*iter).second + amount; + } + + if(new_value < 0) { + alternate_currency[currency_id] = 0; + database.UpdateAltCurrencyValue(CharacterID(), currency_id, 0); + } else { + alternate_currency[currency_id] = new_value; + database.UpdateAltCurrencyValue(CharacterID(), currency_id, new_value); + } + SendAlternateCurrencyValue(currency_id); +} + +void Client::SendAlternateCurrencyValues() +{ + std::list::iterator iter = zone->AlternateCurrencies.begin(); + while(iter != zone->AlternateCurrencies.end()) { + SendAlternateCurrencyValue((*iter).id, false); + ++iter; + } +} + +void Client::SendAlternateCurrencyValue(uint32 currency_id, bool send_if_null) +{ + uint32 value = GetAlternateCurrencyValue(currency_id); + if(value > 0 || (value == 0 && send_if_null)) { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_AltCurrency, sizeof(AltCurrencyUpdate_Struct)); + AltCurrencyUpdate_Struct *update = (AltCurrencyUpdate_Struct*)outapp->pBuffer; + update->opcode = 7; + strcpy(update->name, GetName()); + update->currency_number = currency_id; + update->amount = value; + update->unknown072 = 1; + FastQueuePacket(&outapp); + } +} + +uint32 Client::GetAlternateCurrencyValue(uint32 currency_id) const +{ + std::map::const_iterator iter = alternate_currency.find(currency_id); + if(iter == alternate_currency.end()) { + return 0; + } else { + return (*iter).second; + } +} + +void Client::ProcessAlternateCurrencyQueue() { + while(!alternate_currency_queued_operations.empty()) { + std::pair op = alternate_currency_queued_operations.front(); + + AddAlternateCurrencyValue(op.first, op.second); + + alternate_currency_queued_operations.pop(); + } +} + +void Client::OpenLFGuildWindow() +{ + EQApplicationPacket *outapp = new EQApplicationPacket(OP_LFGuild, 8); + + outapp->WriteUInt32(6); + + FastQueuePacket(&outapp); +} + +bool Client::IsXTarget(const Mob *m) const +{ + if(!XTargettingAvailable() || !m || (m->GetID() == 0)) + return false; + + for(int i = 0; i < GetMaxXTargets(); ++i) + { + if(XTargets[i].ID == m->GetID()) + return true; + } + return false; +} + +bool Client::IsClientXTarget(const Client *c) const +{ + if(!XTargettingAvailable() || !c) + return false; + + for(int i = 0; i < GetMaxXTargets(); ++i) + { + if(!strcasecmp(XTargets[i].Name, c->GetName())) + return true; + } + return false; +} + + +void Client::UpdateClientXTarget(Client *c) +{ + if(!XTargettingAvailable() || !c) + return; + + for(int i = 0; i < GetMaxXTargets(); ++i) + { + if(!strcasecmp(XTargets[i].Name, c->GetName())) + { + XTargets[i].ID = c->GetID(); + SendXTargetPacket(i, c); + } + } +} + +void Client::AddAutoXTarget(Mob *m) +{ + if(!XTargettingAvailable() || !XTargetAutoAddHaters) + return; + + if(IsXTarget(m)) + return; + + for(int i = 0; i < GetMaxXTargets(); ++i) + { + if((XTargets[i].Type == Auto) && (XTargets[i].ID == 0)) + { + XTargets[i].ID = m->GetID(); + SendXTargetPacket(i, m); + break; + } + } +} + +void Client::RemoveXTarget(Mob *m, bool OnlyAutoSlots) +{ + if(!XTargettingAvailable()) + return; + + bool HadFreeAutoSlotsBefore = false; + + int FreedAutoSlots = 0; + + if(m->GetID() == 0) + return; + + for(int i = 0; i < GetMaxXTargets(); ++i) + { + if(OnlyAutoSlots && (XTargets[i].Type !=Auto)) + continue; + + if(XTargets[i].ID == m->GetID()) + { + if(XTargets[i].Type == CurrentTargetNPC) + XTargets[i].Type = Auto; + + if(XTargets[i].Type == Auto) + ++FreedAutoSlots; + + XTargets[i].ID = 0; + + SendXTargetPacket(i, nullptr); + } + else + { + if((XTargets[i].Type == Auto) && (XTargets[i].ID == 0)) + HadFreeAutoSlotsBefore = true; + } + } + // If there are more mobs aggro on us than we had auto-hate slots, add one of those haters into the slot(s) we just freed up. + if(!HadFreeAutoSlotsBefore && FreedAutoSlots) + entity_list.RefreshAutoXTargets(this); +} + +void Client::UpdateXTargetType(XTargetType Type, Mob *m, const char *Name) +{ + if(!XTargettingAvailable()) + return; + + for(int i = 0; i < GetMaxXTargets(); ++i) + { + if(XTargets[i].Type == Type) + { + if(m) + XTargets[i].ID = m->GetID(); + else + XTargets[i].ID = 0; + + if(Name) + strncpy(XTargets[i].Name, Name, 64); + + SendXTargetPacket(i, m); + } + } +} + +void Client::SendXTargetPacket(uint32 Slot, Mob *m) +{ + if(!XTargettingAvailable()) + return; + + uint32 PacketSize = 18; + + if(m) + PacketSize += strlen(m->GetCleanName()); + else + { + PacketSize += strlen(XTargets[Slot].Name); + } + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_XTargetResponse, PacketSize); + outapp->WriteUInt32(GetMaxXTargets()); + outapp->WriteUInt32(1); + outapp->WriteUInt32(Slot); + if(m) + { + outapp->WriteUInt8(1); + } + else + { + if (strlen(XTargets[Slot].Name) && ((XTargets[Slot].Type == CurrentTargetPC) || + (XTargets[Slot].Type == GroupTank) || + (XTargets[Slot].Type == GroupAssist) || + (XTargets[Slot].Type == Puller) || + (XTargets[Slot].Type == RaidAssist1) || + (XTargets[Slot].Type == RaidAssist2) || + (XTargets[Slot].Type == RaidAssist3))) + { + outapp->WriteUInt8(2); + } + else + { + outapp->WriteUInt8(0); + } + } + outapp->WriteUInt32(XTargets[Slot].ID); + outapp->WriteString(m ? m->GetCleanName() : XTargets[Slot].Name); + FastQueuePacket(&outapp); +} + +void Client::RemoveGroupXTargets() +{ + if(!XTargettingAvailable()) + return; + + for(int i = 0; i < GetMaxXTargets(); ++i) + { + if ((XTargets[i].Type == GroupTank) || + (XTargets[i].Type == GroupAssist) || + (XTargets[i].Type == Puller) || + (XTargets[i].Type == RaidAssist1) || + (XTargets[i].Type == RaidAssist2) || + (XTargets[i].Type == RaidAssist3) || + (XTargets[i].Type == GroupMarkTarget1) || + (XTargets[i].Type == GroupMarkTarget2) || + (XTargets[i].Type == GroupMarkTarget3)) + { + XTargets[i].ID = 0; + XTargets[i].Name[0] = 0; + SendXTargetPacket(i, nullptr); + } + } +} + +void Client::RemoveAutoXTargets() +{ + if(!XTargettingAvailable()) + return; + + for(int i = 0; i < GetMaxXTargets(); ++i) + { + if(XTargets[i].Type == Auto) + { + XTargets[i].ID = 0; + XTargets[i].Name[0] = 0; + SendXTargetPacket(i, nullptr); + } + } +} + +void Client::ShowXTargets(Client *c) +{ + if(!c) + return; + + for(int i = 0; i < GetMaxXTargets(); ++i) + c->Message(0, "Xtarget Slot: %i, Type: %2i, ID: %4i, Name: %s", i, XTargets[i].Type, XTargets[i].ID, XTargets[i].Name); +} + +void Client::SetMaxXTargets(uint8 NewMax) +{ + if(!XTargettingAvailable()) + return; + + if(NewMax > XTARGET_HARDCAP) + return; + + MaxXTargets = NewMax; + + Save(0); + + for(int i = MaxXTargets; i < XTARGET_HARDCAP; ++i) + { + XTargets[i].Type = Auto; + XTargets[i].ID = 0; + XTargets[i].Name[0] = 0; + } + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_XTargetResponse, 8); + outapp->WriteUInt32(GetMaxXTargets()); + outapp->WriteUInt32(0); + FastQueuePacket(&outapp); +} + +const char* Client::GetRacePlural(Client* client) { + + switch (client->CastToMob()->GetRace()) { + case HUMAN: + return "Humans"; break; + case BARBARIAN: + return "Barbarians"; break; + case ERUDITE: + return "Erudites"; break; + case WOOD_ELF: + return "Wood Elves"; break; + case HIGH_ELF: + return "High Elves"; break; + case DARK_ELF: + return "Dark Elves"; break; + case HALF_ELF: + return "Half Elves"; break; + case DWARF: + return "Dwarves"; break; + case TROLL: + return "Trolls"; break; + case OGRE: + return "Ogres"; break; + case HALFLING: + return "Halflings"; break; + case GNOME: + return "Gnomes"; break; + case IKSAR: + return "Iksar"; break; + case VAHSHIR: + return "Vah Shir"; break; + case FROGLOK: + return "Frogloks"; break; + case DRAKKIN: + return "Drakkin"; break; + default: + return "Races"; break; + } +} + +const char* Client::GetClassPlural(Client* client) { + + switch (client->CastToMob()->GetClass()) { + case WARRIOR: + return "Warriors"; break; + case CLERIC: + return "Clerics"; break; + case PALADIN: + return "Paladins"; break; + case RANGER: + return "Rangers"; break; + case SHADOWKNIGHT: + return "Shadowknights"; break; + case DRUID: + return "Druids"; break; + case MONK: + return "Monks"; break; + case BARD: + return "Bards"; break; + case ROGUE: + return "Rogues"; break; + case SHAMAN: + return "Shamen"; break; + case NECROMANCER: + return "Necromancers"; break; + case WIZARD: + return "Wizards"; break; + case MAGICIAN: + return "Magicians"; break; + case ENCHANTER: + return "Enchanters"; break; + case BEASTLORD: + return "Beastlords"; break; + case BERSERKER: + return "Berserkers"; break; + default: + return "Classes"; break; + } +} + + +void Client::SendWebLink(const char *website) +{ + size_t len = strlen(website) + 1; + if(website != 0 && len > 1) + { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_Weblink, sizeof(Weblink_Struct) + len); + Weblink_Struct *wl = (Weblink_Struct*)outapp->pBuffer; + memcpy(wl->weblink, website, len); + wl->weblink[len] = '\0'; + + FastQueuePacket(&outapp); + } +} + +void Client::SendMercPersonalInfo() +{ + uint32 mercTypeCount = 1; + uint32 mercCount = 1; //TODO: Un-hardcode this and support multiple mercs like in later clients than SoD. + //uint32 packetSize = 0; + uint32 i=0; + uint32 altCurrentType = 19; //TODO: Implement alternate currency purchases involving mercs! + + if (GetClientVersion() >= EQClientRoF) + { + MercTemplate *mercData = &zone->merc_templates[GetMercInfo().MercTemplateID]; + + if (mercData) + { + int i = 0; + int stancecount = 0; + stancecount += zone->merc_stance_list[GetMercInfo().MercTemplateID].size(); + + if(stancecount > MAX_MERC_STANCES || mercCount > MAX_MERC || mercTypeCount > MAX_MERC_GRADES) + { + SendMercMerchantResponsePacket(0); + return; + } + if (mercCount > 0 && mercCount) + { + EQApplicationPacket *outapp = new EQApplicationPacket(OP_MercenaryDataUpdate, sizeof(MercenaryDataUpdate_Struct)); + MercenaryDataUpdate_Struct* mdus = (MercenaryDataUpdate_Struct*)outapp->pBuffer; + mdus->MercStatus = 0; + mdus->MercCount = mercCount; + mdus->MercData[i].MercID = mercData->MercTemplateID; + mdus->MercData[i].MercType = mercData->MercType; + mdus->MercData[i].MercSubType = mercData->MercSubType; + mdus->MercData[i].PurchaseCost = Merc::CalcPurchaseCost(mercData->MercTemplateID, GetLevel(), 0); + mdus->MercData[i].UpkeepCost = Merc::CalcUpkeepCost(mercData->MercTemplateID, GetLevel(), 0); + mdus->MercData[i].Status = 0; + mdus->MercData[i].AltCurrencyCost = Merc::CalcPurchaseCost(mercData->MercTemplateID, GetLevel(), altCurrentType); + mdus->MercData[i].AltCurrencyUpkeep = Merc::CalcPurchaseCost(mercData->MercTemplateID, GetLevel(), altCurrentType); + mdus->MercData[i].AltCurrencyType = altCurrentType; + mdus->MercData[i].MercUnk01 = 0; + mdus->MercData[i].TimeLeft = GetMercInfo().MercTimerRemaining; //GetMercTimer().GetRemainingTime(); + mdus->MercData[i].MerchantSlot = i + 1; + mdus->MercData[i].MercUnk02 = 1; + mdus->MercData[i].StanceCount = zone->merc_stance_list[mercData->MercTemplateID].size(); + mdus->MercData[i].MercUnk03 = 0; + mdus->MercData[i].MercUnk04 = 1; + strn0cpy(mdus->MercData[i].MercName, GetMercInfo().merc_name , sizeof(mdus->MercData[i].MercName)); + uint32 stanceindex = 0; + if (mdus->MercData[i].StanceCount != 0) + { + std::list::iterator iter = zone->merc_stance_list[mercData->MercTemplateID].begin(); + while(iter != zone->merc_stance_list[mercData->MercTemplateID].end()) + { + mdus->MercData[i].Stances[stanceindex].StanceIndex = stanceindex; + mdus->MercData[i].Stances[stanceindex].Stance = (iter->StanceID); + stanceindex++; + ++iter; + } + } + + mdus->MercData[i].MercUnk05 = 1; + FastQueuePacket(&outapp); + return; + } + } + } + else + { + int stancecount = 0; + stancecount += zone->merc_stance_list[GetMercInfo().MercTemplateID].size(); + + if(mercCount > MAX_MERC || mercTypeCount > MAX_MERC_GRADES) + { + if (GetClientVersion() == EQClientSoD) + { + SendMercMerchantResponsePacket(0); + } + return; + } + + EQApplicationPacket *outapp = new EQApplicationPacket(OP_MercenaryDataResponse, sizeof(MercenaryMerchantList_Struct)); + MercenaryMerchantList_Struct* mml = (MercenaryMerchantList_Struct*)outapp->pBuffer; + MercTemplate *mercData = &zone->merc_templates[GetMercInfo().MercTemplateID]; + + + if(mercData) + { + if(mercTypeCount > 0) + { + mml->MercTypeCount = mercTypeCount; //We only should have one merc entry. + mml->MercGrades[i] = 1; + } + mml->MercCount = mercCount; + if(mercCount > 0) + { + + mml->Mercs[i].MercID = mercData->MercTemplateID; + mml->Mercs[i].MercType = mercData->MercType; + mml->Mercs[i].MercSubType = mercData->MercSubType; + mml->Mercs[i].PurchaseCost = RuleB(Mercs, ChargeMercPurchaseCost) ? Merc::CalcPurchaseCost(mercData->MercTemplateID, GetLevel(), 0): 0; + mml->Mercs[i].UpkeepCost = RuleB(Mercs, ChargeMercUpkeepCost) ? Merc::CalcUpkeepCost(mercData->MercTemplateID, GetLevel(), 0): 0; + mml->Mercs[i].Status = 0; + mml->Mercs[i].AltCurrencyCost = RuleB(Mercs, ChargeMercPurchaseCost) ? Merc::CalcPurchaseCost(mercData->MercTemplateID, GetLevel(), altCurrentType): 0; + mml->Mercs[i].AltCurrencyUpkeep = RuleB(Mercs, ChargeMercUpkeepCost) ? Merc::CalcUpkeepCost(mercData->MercTemplateID, GetLevel(), altCurrentType): 0; + mml->Mercs[i].AltCurrencyType = altCurrentType; + mml->Mercs[i].MercUnk01 = 0; + mml->Mercs[i].TimeLeft = GetMercInfo().MercTimerRemaining; + mml->Mercs[i].MerchantSlot = i + 1; + mml->Mercs[i].MercUnk02 = 1; + mml->Mercs[i].StanceCount = zone->merc_stance_list[mercData->MercTemplateID].size(); + mml->Mercs[i].MercUnk03 = 0; + mml->Mercs[i].MercUnk04 = 1; + //mml->Mercs[i].MercName; + int stanceindex = 0; + if(mml->Mercs[i].StanceCount != 0) + { + std::list::iterator iter = zone->merc_stance_list[mercData->MercTemplateID].begin(); + while(iter != zone->merc_stance_list[mercData->MercTemplateID].end()) + { + mml->Mercs[i].Stances[stanceindex].StanceIndex = stanceindex; + mml->Mercs[i].Stances[stanceindex].Stance = (iter->StanceID); + stanceindex++; + ++iter; + } + } + FastQueuePacket(&outapp); + } + else + { + safe_delete(outapp); + if (GetClientVersion() == EQClientSoD) + { + SendMercMerchantResponsePacket(0); + } + return; + } + if (GetClientVersion() == EQClientSoD) + { + SendMercMerchantResponsePacket(0); + } + } + else + { + safe_delete(outapp); + if (GetClientVersion() == EQClientSoD) + { + SendMercMerchantResponsePacket(0); + } + return; + } + } +} + +void Client::SendClearMercInfo() +{ + EQApplicationPacket* outapp = new EQApplicationPacket(OP_MercenaryDataUpdate, sizeof(NoMercenaryHired_Struct)); + NoMercenaryHired_Struct *nmhs = (NoMercenaryHired_Struct*)outapp->pBuffer; + nmhs->MercStatus = -1; + nmhs->MercCount = 0; + nmhs->MercID = 1; + FastQueuePacket(&outapp); +} + + +void Client::DuplicateLoreMessage(uint32 ItemID) +{ + if(!(ClientVersionBit & BIT_RoFAndLater)) + { + Message_StringID(0, PICK_LORE); + return; + } + + const Item_Struct *item = database.GetItem(ItemID); + + if(!item) + return; + + Message_StringID(0, PICK_LORE, item->Name); +} + +void Client::GarbleMessage(char *message, uint8 variance) +{ + // Garble message by variance% + const char alpha_list[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; // only change alpha characters for now + + for (size_t i = 0; i < strlen(message); i++) { + uint8 chance = (uint8)MakeRandomInt(0, 115); // variation just over worst possible scrambling + if (isalpha(message[i]) && (chance <= variance)) { + uint8 rand_char = (uint8)MakeRandomInt(0,51); // choose a random character from the alpha list + message[i] = alpha_list[rand_char]; + } + } +} + +// returns what Other thinks of this +FACTION_VALUE Client::GetReverseFactionCon(Mob* iOther) { + if (GetOwnerID()) { + return GetOwnerOrSelf()->GetReverseFactionCon(iOther); + } + + iOther = iOther->GetOwnerOrSelf(); + + if (iOther->GetPrimaryFaction() < 0) + return GetSpecialFactionCon(iOther); + + if (iOther->GetPrimaryFaction() == 0) + return FACTION_INDIFFERENT; + + return GetFactionLevel(CharacterID(), 0, GetRace(), GetClass(), GetDeity(), iOther->GetPrimaryFaction(), iOther); +} + +//o-------------------------------------------------------------- +//| Name: GetFactionLevel; rembrant, Dec. 16, 2001 +//o-------------------------------------------------------------- +//| Notes: Gets the characters faction standing with the specified NPC. +//| Will return Indifferent on failure. +//o-------------------------------------------------------------- +FACTION_VALUE Client::GetFactionLevel(uint32 char_id, uint32 npc_id, uint32 p_race, uint32 p_class, uint32 p_deity, int32 pFaction, Mob* tnpc) +{ + if (pFaction < 0) + return GetSpecialFactionCon(tnpc); + FACTION_VALUE fac = FACTION_INDIFFERENT; + int32 tmpFactionValue; + FactionMods fmods; + + // neotokyo: few optimizations + if (GetFeigned()) + return FACTION_INDIFFERENT; + if (invisible_undead && tnpc && !tnpc->SeeInvisibleUndead()) + return FACTION_INDIFFERENT; + if (IsInvisible(tnpc)) + return FACTION_INDIFFERENT; + if (tnpc && tnpc->GetOwnerID() != 0) // pets con amiably to owner and indiff to rest + { + if (char_id == tnpc->GetOwner()->CastToClient()->CharacterID()) + return FACTION_AMIABLE; + else + return FACTION_INDIFFERENT; + } + + //First get the NPC's Primary faction + if(pFaction > 0) + { + //Get the faction data from the database + if(database.GetFactionData(&fmods, p_class, p_race, p_deity, pFaction)) + { + //Get the players current faction with pFaction + tmpFactionValue = GetCharacterFactionLevel(pFaction); + // Everhood - tack on any bonuses from Alliance type spell effects + tmpFactionValue += GetFactionBonus(pFaction); + tmpFactionValue += GetItemFactionBonus(pFaction); + //Return the faction to the client + fac = CalculateFaction(&fmods, tmpFactionValue); + } + } + else + { + return(FACTION_INDIFFERENT); + } + + // merchant fix + if (tnpc && tnpc->IsNPC() && tnpc->CastToNPC()->MerchantType && (fac == FACTION_THREATENLY || fac == FACTION_SCOWLS)) + fac = FACTION_DUBIOUS; + + if (tnpc != 0 && fac != FACTION_SCOWLS && tnpc->CastToNPC()->CheckAggro(this)) + fac = FACTION_THREATENLY; + + return fac; +} + +//o-------------------------------------------------------------- +//| Name: SetFactionLevel; rembrant, Dec. 20, 2001 +//o-------------------------------------------------------------- +//| Notes: Sets the characters faction standing with the specified NPC. +//o-------------------------------------------------------------- +void Client::SetFactionLevel(uint32 char_id, uint32 npc_id, uint8 char_class, uint8 char_race, uint8 char_deity) +{ + int32 faction_id[MAX_NPC_FACTIONS]={ 0,0,0,0,0,0,0,0,0,0 }; + int32 npc_value[MAX_NPC_FACTIONS]={ 0,0,0,0,0,0,0,0,0,0 }; + uint8 temp[MAX_NPC_FACTIONS]={ 0,0,0,0,0,0,0,0,0,0 }; + int32 mod; + int32 t; + int32 tmpValue; + int32 current_value; + FactionMods fm; + // Get the npc faction list + if(!database.GetNPCFactionList(npc_id, faction_id, npc_value, temp)) + return; + for(int i = 0;iitembonuses.HeroicCHA) { + int faction_mod = itembonuses.HeroicCHA / 5; + // If our result isn't truncated, then just do that + if(npc_value[i] * faction_mod / 100 != 0) + npc_value[i] += npc_value[i] * faction_mod / 100; + // If our result is truncated, then double a mob's value every once and a while to equal what they would have got + else { + if(MakeRandomInt(0, 100) < faction_mod) + npc_value[i] *= 2; + } + } + //figure out their modifier + mod = fm.base + fm.class_mod + fm.race_mod + fm.deity_mod; + if(mod > MAX_FACTION) + mod = MAX_FACTION; + else if(mod < MIN_FACTION) + mod = MIN_FACTION; + + // Calculate the faction + if(npc_value[i] != 0) { + tmpValue = current_value + mod + npc_value[i]; + + int16 FactionModPct = spellbonuses.FactionModPct + itembonuses.FactionModPct + aabonuses.FactionModPct; + tmpValue += (tmpValue * FactionModPct) / 100; + + // Make sure faction hits don't go to GMs... + if (m_pp.gm==1 && (tmpValue < current_value)) { + tmpValue = current_value; + } + + // Make sure we dont go over the min/max faction limits + if(tmpValue >= MAX_FACTION) + { + t = MAX_FACTION - mod; + if(current_value == t) { + //do nothing, it is already maxed out + } else if(!(database.SetCharacterFactionLevel(char_id, faction_id[i], t, temp[i], factionvalues))) + { + return; + } + } + else if(tmpValue <= MIN_FACTION) + { + t = MIN_FACTION - mod; + if(current_value == t) { + //do nothing, it is already maxed out + } else if(!(database.SetCharacterFactionLevel(char_id, faction_id[i], t, temp[i], factionvalues))) + { + return; + } + } + else + { + if(!(database.SetCharacterFactionLevel(char_id, faction_id[i], current_value + npc_value[i], temp[i], factionvalues))) + { + return; + } + } + if(tmpValue <= MIN_FACTION) + tmpValue = MIN_FACTION; + + SendFactionMessage(npc_value[i], faction_id[i], tmpValue, temp[i]); + } + } + } + return; +} + +void Client::SetFactionLevel2(uint32 char_id, int32 faction_id, uint8 char_class, uint8 char_race, uint8 char_deity, int32 value, uint8 temp) +{ + int32 current_value; + //Get the npc faction list + if(faction_id > 0 && value != 0) { + //Get the faction modifiers + current_value = GetCharacterFactionLevel(faction_id) + value; + if(!(database.SetCharacterFactionLevel(char_id, faction_id, current_value, temp, factionvalues))) + return; + + SendFactionMessage(value, faction_id, current_value, temp); + } + return; +} + +int32 Client::GetCharacterFactionLevel(int32 faction_id) +{ + if (faction_id <= 0) + return 0; + faction_map::iterator res; + res = factionvalues.find(faction_id); + if(res == factionvalues.end()) + return(0); + return(res->second); +} + +// returns the character's faction level, adjusted for racial, class, and deity modifiers +int32 Client::GetModCharacterFactionLevel(int32 faction_id) { + int32 Modded = GetCharacterFactionLevel(faction_id); + FactionMods fm; + if(database.GetFactionData(&fm,GetClass(),GetRace(),GetDeity(),faction_id)) + Modded += fm.base + fm.class_mod + fm.race_mod + fm.deity_mod; + if (Modded > MAX_FACTION) + Modded = MAX_FACTION; + + return Modded; +} + +bool Client::HatedByClass(uint32 p_race, uint32 p_class, uint32 p_deity, int32 pFaction) +{ + + bool Result = false; + + int32 tmpFactionValue; + FactionMods fmods; + + //First get the NPC's Primary faction + if(pFaction > 0) + { + //Get the faction data from the database + if(database.GetFactionData(&fmods, p_class, p_race, p_deity, pFaction)) + { + tmpFactionValue = GetCharacterFactionLevel(pFaction); + tmpFactionValue += GetFactionBonus(pFaction); + tmpFactionValue += GetItemFactionBonus(pFaction); + CalculateFaction(&fmods, tmpFactionValue); + if(fmods.class_mod < fmods.race_mod) + Result = true; + } + } + return Result; +} + +//o-------------------------------------------------------------- +//| Name: SendFactionMessage +//o-------------------------------------------------------------- +//| Purpose: Send faction change message to client +//o-------------------------------------------------------------- +void Client::SendFactionMessage(int32 tmpvalue, int32 faction_id, int32 totalvalue, uint8 temp) +{ + char name[50]; + + // default to Faction# if we couldn't get the name from the ID + if (database.GetFactionName(faction_id, name, sizeof(name)) == false) + snprintf(name, sizeof(name),"Faction%i",faction_id); + + if (tmpvalue == 0 || temp == 1 || temp == 2) + return; + else if (totalvalue >= MAX_FACTION) + Message_StringID(0, FACTION_BEST, name); + else if (tmpvalue > 0 && totalvalue < MAX_FACTION) + Message_StringID(0, FACTION_BETTER, name); + else if (tmpvalue < 0 && totalvalue > MIN_FACTION) + Message_StringID(0, FACTION_WORSE, name); + else if (totalvalue <= MIN_FACTION) + Message_StringID(0, FACTION_WORST, name); + + return; +} + +void Client::LoadAccountFlags() +{ + accountflags.clear(); + + std::string query = StringFormat("SELECT p_flag, p_value FROM account_flags WHERE p_accid = '%d'", account_id); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + std::cerr << "Error in LoadAccountFlags query '" << query << "' " << results.ErrorMessage() << std::endl; + return; + } + + for (auto row = results.begin(); row != results.end(); ++row) { + std::string fname(row[0]); + std::string fval(row[1]); + accountflags[fname] = fval; + } + +} + +void Client::SetAccountFlag(std::string flag, std::string val) +{ + std::string query = StringFormat("REPLACE INTO account_flags (p_accid, p_flag, p_value) " + "VALUES( '%d', '%s', '%s')", account_id, flag.c_str(), val.c_str()); + auto results = database.QueryDatabase(query); + if(!results.Success()) + std::cerr << "Error in SetAccountFlags query '" << query << "' " << results.ErrorMessage() << std::endl; + + accountflags[flag] = val; +} + +std::string Client::GetAccountFlag(std::string flag) +{ + return(accountflags[flag]); +} + +void Client::TickItemCheck() +{ + int i; + + if(zone->tick_items.empty()) { return; } + + //Scan equip slots for items + for(i = EmuConstants::EQUIPMENT_BEGIN; i <= EmuConstants::EQUIPMENT_END; i++) + { + TryItemTick(i); + } + //Scan main inventory + cursor + for(i = EmuConstants::GENERAL_BEGIN; i <= MainCursor; i++) + { + TryItemTick(i); + } + //Scan bags + for(i = EmuConstants::GENERAL_BAGS_BEGIN; i <= EmuConstants::CURSOR_BAG_END; i++) + { + TryItemTick(i); + } +} + +void Client::TryItemTick(int slot) +{ + int iid = 0; + const ItemInst* inst = m_inv[slot]; + if(inst == 0) { return; } + + iid = inst->GetID(); + + if(zone->tick_items.count(iid) > 0) + { + if( GetLevel() >= zone->tick_items[iid].level && MakeRandomInt(0, 100) >= (100 - zone->tick_items[iid].chance) && (zone->tick_items[iid].bagslot || slot <= EmuConstants::EQUIPMENT_END) ) + { + ItemInst* e_inst = (ItemInst*)inst; + parse->EventItem(EVENT_ITEM_TICK, this, e_inst, nullptr, "", slot); + } + } + + //Only look at augs in main inventory + if(slot > EmuConstants::EQUIPMENT_END) { return; } + + for (int x = AUG_BEGIN; x < EmuConstants::ITEM_COMMON_SIZE; ++x) + { + ItemInst * a_inst = inst->GetAugment(x); + if(!a_inst) { continue; } + + iid = a_inst->GetID(); + + if(zone->tick_items.count(iid) > 0) + { + if( GetLevel() >= zone->tick_items[iid].level && MakeRandomInt(0, 100) >= (100 - zone->tick_items[iid].chance) ) + { + ItemInst* e_inst = (ItemInst*)a_inst; + parse->EventItem(EVENT_ITEM_TICK, this, e_inst, nullptr, "", slot); + } + } + } +} + +void Client::ItemTimerCheck() +{ + int i; + for(i = EmuConstants::EQUIPMENT_BEGIN; i <= EmuConstants::EQUIPMENT_END; i++) + { + TryItemTimer(i); + } + + for(i = EmuConstants::GENERAL_BAGS_BEGIN; i <= MainCursor; i++) + { + TryItemTimer(i); + } + + for(i = EmuConstants::GENERAL_BAGS_BEGIN; i <= EmuConstants::CURSOR_BAG_END; i++) + { + TryItemTimer(i); + } +} + +void Client::TryItemTimer(int slot) +{ + ItemInst* inst = m_inv.GetItem(slot); + if(!inst) { + return; + } + + auto item_timers = inst->GetTimers(); + auto it_iter = item_timers.begin(); + while(it_iter != item_timers.end()) { + if(it_iter->second.Check()) { + parse->EventItem(EVENT_TIMER, this, inst, nullptr, it_iter->first, 0); + } + ++it_iter; + } + + if(slot > EmuConstants::EQUIPMENT_END) { + return; + } + + for (int x = AUG_BEGIN; x < EmuConstants::ITEM_COMMON_SIZE; ++x) + { + ItemInst * a_inst = inst->GetAugment(x); + if(!a_inst) { + continue; + } + + auto item_timers = a_inst->GetTimers(); + auto it_iter = item_timers.begin(); + while(it_iter != item_timers.end()) { + if(it_iter->second.Check()) { + parse->EventItem(EVENT_TIMER, this, a_inst, nullptr, it_iter->first, 0); + } + ++it_iter; + } + } +} + +void Client::RefundAA() { + int cur = 0; + bool refunded = false; + + for(int x = 0; x < aaHighestID; x++) { + cur = GetAA(x); + if(cur > 0){ + SendAA_Struct* curaa = zone->FindAA(x); + if(cur){ + SetAA(x, 0); + for(int j = 0; j < cur; j++) { + m_pp.aapoints += curaa->cost + (curaa->cost_inc * j); + refunded = true; + } + } + else + { + m_pp.aapoints += cur; + SetAA(x, 0); + refunded = true; + } + } + } + + if(refunded) { + Save(); + Kick(); + } +} + +void Client::IncrementAA(int aa_id) { + SendAA_Struct* aa2 = zone->FindAA(aa_id); + + if(aa2 == nullptr) + return; + + if(GetAA(aa_id) == aa2->max_level) + return; + + SetAA(aa_id, GetAA(aa_id) + 1); + + Save(); + + SendAA(aa_id); + SendAATable(); + SendAAStats(); + CalcBonuses(); +} + +void Client::SendItemScale(ItemInst *inst) { + int slot = m_inv.GetSlotByItemInst(inst); + if(slot != -1) { + inst->ScaleItem(); + SendItemPacket(slot, inst, ItemPacketCharmUpdate); + CalcBonuses(); + } +} + +void Client::AddRespawnOption(std::string option_name, uint32 zoneid, float x, float y, float z, float heading, bool initial_selection, int8 position) +{ + //If respawn window is already open, any changes would create an inconsistency with the client + if (IsHoveringForRespawn()) { return; } + + if (zoneid == 0) + zoneid = zone->GetZoneID(); + + //Create respawn option + RespawnOption res_opt; + res_opt.name = option_name; + res_opt.zoneid = zoneid; + res_opt.x = x; + res_opt.y = y; + res_opt.z = z; + res_opt.heading = heading; + + if (position == -1 || position >= respawn_options.size()) + { + //No position specified, or specified beyond the end, simply append + respawn_options.push_back(res_opt); + //Make this option the initial selection for the window if desired + if (initial_selection) + initial_respawn_selection = static_cast(respawn_options.size()) - 1; + } + else if (position == 0) + { + respawn_options.push_front(res_opt); + if (initial_selection) + initial_respawn_selection = 0; + } + else + { + //Insert new option between existing options + std::list::iterator itr; + uint8 pos = 0; + for (itr = respawn_options.begin(); itr != respawn_options.end(); ++itr) + { + if (pos++ == position) + { + respawn_options.insert(itr,res_opt); + //Make this option the initial selection for the window if desired + if (initial_selection) + initial_respawn_selection = pos; + return; + } + } + } +} + +bool Client::RemoveRespawnOption(std::string option_name) +{ + //If respawn window is already open, any changes would create an inconsistency with the client + if (IsHoveringForRespawn() || respawn_options.empty()) { return false; } + + bool had = false; + RespawnOption* opt; + std::list::iterator itr; + for (itr = respawn_options.begin(); itr != respawn_options.end(); ++itr) + { + opt = &(*itr); + if (opt->name.compare(option_name) == 0) + { + itr = respawn_options.erase(itr); + had = true; + //could be more with the same name, so keep going... + } + } + return had; +} + +bool Client::RemoveRespawnOption(uint8 position) +{ + //If respawn window is already open, any changes would create an inconsistency with the client + if (IsHoveringForRespawn() || respawn_options.empty()) { return false; } + + //Easy cases first... + if (position == 0) + { + respawn_options.pop_front(); + return true; + } + else if (position == (respawn_options.size() - 1)) + { + respawn_options.pop_back(); + return true; + } + + std::list::iterator itr; + uint8 pos = 0; + for (itr = respawn_options.begin(); itr != respawn_options.end(); ++itr) + { + if (pos++ == position) + { + respawn_options.erase(itr); + return true; + } + } + return false; +} + +void Client::SetHunger(int32 in_hunger) +{ + EQApplicationPacket *outapp; + outapp = new EQApplicationPacket(OP_Stamina, sizeof(Stamina_Struct)); + Stamina_Struct* sta = (Stamina_Struct*)outapp->pBuffer; + sta->food = in_hunger; + sta->water = m_pp.thirst_level > 6000 ? 6000 : m_pp.thirst_level; + + m_pp.hunger_level = in_hunger; + + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::SetThirst(int32 in_thirst) +{ + EQApplicationPacket *outapp; + outapp = new EQApplicationPacket(OP_Stamina, sizeof(Stamina_Struct)); + Stamina_Struct* sta = (Stamina_Struct*)outapp->pBuffer; + sta->food = m_pp.hunger_level > 6000 ? 6000 : m_pp.hunger_level; + sta->water = in_thirst; + + m_pp.thirst_level = in_thirst; + + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::SetConsumption(int32 in_hunger, int32 in_thirst) +{ + EQApplicationPacket *outapp; + outapp = new EQApplicationPacket(OP_Stamina, sizeof(Stamina_Struct)); + Stamina_Struct* sta = (Stamina_Struct*)outapp->pBuffer; + sta->food = in_hunger; + sta->water = in_thirst; + + m_pp.hunger_level = in_hunger; + m_pp.thirst_level = in_thirst; + + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::Consume(const Item_Struct *item, uint8 type, int16 slot, bool auto_consume) +{ + if(!item) { return; } + + uint16 cons_mod = 180; + + int16 metabolism_bonus = spellbonuses.Metabolism + itembonuses.Metabolism + aabonuses.Metabolism; + + if (metabolism_bonus) + cons_mod = cons_mod * metabolism_bonus * RuleI(Character, ConsumptionMultiplier) / 10000; + else + cons_mod = cons_mod * RuleI(Character, ConsumptionMultiplier) / 100; + + if(type == ItemTypeFood) + { + int hchange = item->CastTime * cons_mod; + hchange = mod_food_value(item, hchange); + + if(hchange < 0) { return; } + + m_pp.hunger_level += hchange; + DeleteItemInInventory(slot, 1, false); + + if(!auto_consume) //no message if the client consumed for us + entity_list.MessageClose_StringID(this, true, 50, 0, EATING_MESSAGE, GetName(), item->Name); + +#if EQDEBUG >= 5 + LogFile->write(EQEMuLog::Debug, "Eating from slot:%i", (int)slot); +#endif + } + else + { + int tchange = item->CastTime * cons_mod; + tchange = mod_drink_value(item, tchange); + + if(tchange < 0) { return; } + + m_pp.thirst_level += tchange; + DeleteItemInInventory(slot, 1, false); + + if(!auto_consume) //no message if the client consumed for us + entity_list.MessageClose_StringID(this, true, 50, 0, DRINKING_MESSAGE, GetName(), item->Name); + +#if EQDEBUG >= 5 + LogFile->write(EQEMuLog::Debug, "Drinking from slot:%i", (int)slot); +#endif + } +} + +void Client::SendMarqueeMessage(uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, std::string msg) +{ + if(duration == 0 || msg.length() == 0) { + return; + } + + EQApplicationPacket outapp(OP_Marquee, sizeof(ClientMarqueeMessage_Struct) + msg.length()); + ClientMarqueeMessage_Struct *cms = (ClientMarqueeMessage_Struct*)outapp.pBuffer; + + cms->type = type; + cms->unk04 = 10; + cms->priority = priority; + cms->fade_in_time = fade_in; + cms->fade_out_time = fade_out; + cms->duration = duration; + strcpy(cms->msg, msg.c_str()); + + QueuePacket(&outapp); +} + +void Client::PlayMP3(const char* fname) +{ + std::string filename = fname; + EQApplicationPacket *outapp = new EQApplicationPacket(OP_PlayMP3, filename.length() + 1); + PlayMP3_Struct* buf = (PlayMP3_Struct*)outapp->pBuffer; + strncpy(buf->filename, fname, filename.length()); + QueuePacket(outapp); + safe_delete(outapp); +} + +void Client::ExpeditionSay(const char *str, int expID) { + + std::string query = StringFormat("SELECT `player_name` FROM " + "`cust_inst_players` WHERE " + "`inst_id` = %i", expID); + auto results = database.QueryDatabase(query); + if (!results.Success()) + return; + + this->Message(14, "You say to the expedition, '%s'", str); + + for (auto row = results.begin(); row != results.end(); ++row) { + const char* charName = row[0]; + if(strcmp(charName, this->GetCleanName()) != 0) + worldserver.SendEmoteMessage(charName, 0, 0, 14, "%s says to the expedition, '%s'", this->GetCleanName(), str); + } + +} diff --git a/zone/client.h b/zone/client.h index b78530ae1..d3aed5540 100644 --- a/zone/client.h +++ b/zone/client.h @@ -268,7 +268,7 @@ public: void TradeRequestFailed(const EQApplicationPacket* app); void BuyTraderItem(TraderBuy_Struct* tbs,Client* trader,const EQApplicationPacket* app); void TraderUpdate(uint16 slot_id,uint32 trader_id); - void FinishTrade(Mob* with, ServerPacket* qspack = nullptr, bool finalizer = false); + void FinishTrade(Mob* with, bool finalizer = false, void* event_entry = nullptr, std::list* event_details = nullptr); void SendZonePoints(); void SendBuyerResults(char *SearchQuery, uint32 SearchID); @@ -1097,7 +1097,7 @@ public: inline void ClearDraggedCorpses() { DraggedCorpses.clear(); } void SendAltCurrencies(); void SetAlternateCurrencyValue(uint32 currency_id, uint32 new_amount); - void AddAlternateCurrencyValue(uint32 currency_id, int32 amount); + void AddAlternateCurrencyValue(uint32 currency_id, int32 amount, int8 method = 0); void SendAlternateCurrencyValues(); void SendAlternateCurrencyValue(uint32 currency_id, bool send_if_null = true); uint32 GetAlternateCurrencyValue(uint32 currency_id) const; diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index ce6b1a323..4314b1c5a 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -16,16 +16,16 @@ */ #include "../common/debug.h" -#include -#include -#include -#include -#include -#include -#include #include -#include +#include +#include +#include #include +#include +#include +#include +#include +#include #ifdef _WINDOWS #define snprintf _snprintf @@ -38,8 +38,6 @@ #include #endif -#include "masterentity.h" -#include "zonedb.h" #include "../common/packet_functions.h" #include "../common/packet_dump.h" #include "worldserver.h" @@ -59,17 +57,21 @@ #include "../common/faction.h" #include "../common/crc32.h" #include "string_ids.h" -#include "map.h" #include "titles.h" -#include "pets.h" +#include "water_map.h" +#include "worldserver.h" +#include "zone.h" #include "zone_config.h" #include "guild_mgr.h" #include "pathing.h" #include "water_map.h" #include "merc.h" +#include "pets.h" #include "../common/zone_numbers.h" #include "quest_parser_collection.h" +#include "queryserv.h" +extern QueryServ* QServ; extern Zone* zone; extern volatile bool ZoneLoaded; extern WorldServer worldserver; @@ -431,7 +433,7 @@ int Client::HandlePacket(const EQApplicationPacket *app) case CLIENT_CONNECTING: { if(ConnectingOpcodes.count(opcode) != 1) { //Hate const cast but everything in lua needs to be non-const even if i make it non-mutable - std::vector args; + std::vector args; args.push_back(const_cast(app)); parse->EventPlayer(EVENT_UNHANDLED_OPCODE, this, "", 1, &args); @@ -460,7 +462,7 @@ int Client::HandlePacket(const EQApplicationPacket *app) ClientPacketProc p; p = ConnectedOpcodes[opcode]; if(p == nullptr) { - std::vector args; + std::vector args; args.push_back(const_cast(app)); parse->EventPlayer(EVENT_UNHANDLED_OPCODE, this, "", 0, &args); @@ -3135,6 +3137,7 @@ void Client::Handle_OP_ItemLinkClick(const EQApplicationPacket *app) DumpPacket(app); return; } + DumpPacket(app); ItemViewRequest_Struct* ivrs = (ItemViewRequest_Struct*)app->pBuffer; @@ -3154,30 +3157,24 @@ void Client::Handle_OP_ItemLinkClick(const EQApplicationPacket *app) silentsaylink = true; } - if (sayid && sayid > 0) + if (sayid > 0) { - char errbuf[MYSQL_ERRMSG_SIZE]; - char *query = 0; - MYSQL_RES *result; - MYSQL_ROW row; + std::string query = StringFormat("SELECT `phrase` FROM saylink WHERE `id` = '%i'", sayid); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + Message(13, "Error: The saylink (%s) was not found in the database.", response.c_str()); + return; + } - if(database.RunQuery(query,MakeAnyLenString(&query,"SELECT `phrase` FROM saylink WHERE `id` = '%i'", sayid),errbuf,&result)) - { - if (mysql_num_rows(result) == 1) - { - row = mysql_fetch_row(result); - response = row[0]; - } - mysql_free_result(result); - } - else - { - Message(13, "Error: The saylink (%s) was not found in the database.",response.c_str()); - safe_delete_array(query); + if (results.RowCount() != 1) { + Message(13, "Error: The saylink (%s) was not found in the database.", response.c_str()); return; } - safe_delete_array(query); + + auto row = results.begin(); + response = row[0]; + } if((response).size() > 0) @@ -4626,7 +4623,7 @@ void Client::Handle_OP_CastSpell(const EQApplicationPacket *app) p_timers.Start(pTimerHarmTouch, HarmTouchReuseTime); } - + if (spell_to_cast > 0) // if we've matched LoH or HT, cast now CastSpell(spell_to_cast, castspell->target_id, castspell->slot); } @@ -4860,6 +4857,7 @@ void Client::Handle_OP_TradeAcceptClick(const EQApplicationPacket *app) { Mob* with = trade->With(); trade->state = TradeAccepted; + if (with && with->IsClient()) { //finish trade... // Have both accepted? @@ -4870,6 +4868,7 @@ void Client::Handle_OP_TradeAcceptClick(const EQApplicationPacket *app) other->trade->state = TradeCompleting; trade->state = TradeCompleting; + // should we do this for NoDrop items as well? if (CheckTradeLoreConflict(other) || other->CheckTradeLoreConflict(this)) { Message_StringID(13, TRADE_CANCEL_LORE); other->Message_StringID(13, TRADE_CANCEL_LORE); @@ -4885,23 +4884,38 @@ void Client::Handle_OP_TradeAcceptClick(const EQApplicationPacket *app) // start QS code if(RuleB(QueryServ, PlayerLogTrades)) { - uint16 trade_count = 0; + QSPlayerLogTrade_Struct event_entry; + std::list event_details; - // Item trade count for packet sizing - for(int16 slot_id = EmuConstants::TRADE_BEGIN; slot_id <= EmuConstants::TRADE_END; slot_id++) { - if(other->GetInv().GetItem(slot_id)) { trade_count += other->GetInv().GetItem(slot_id)->GetTotalItemCount(); } - if(m_inv[slot_id]) { trade_count += m_inv[slot_id]->GetTotalItemCount(); } - } - - ServerPacket* qspack = new ServerPacket(ServerOP_QSPlayerLogTrades, sizeof(QSPlayerLogTrade_Struct) + (sizeof(QSTradeItems_Struct) * trade_count)); + memset(&event_entry, 0, sizeof(QSPlayerLogTrade_Struct)); // Perform actual trade - this->FinishTrade(other, qspack, true); - other->FinishTrade(this, qspack, false); + this->FinishTrade(other, true, &event_entry, &event_details); + other->FinishTrade(this, false, &event_entry, &event_details); - qspack->Deflate(); - if(worldserver.Connected()) { worldserver.SendPacket(qspack); } - safe_delete(qspack); + event_entry._detail_count = event_details.size(); + + ServerPacket* qs_pack = new ServerPacket(ServerOP_QSPlayerLogTrades, sizeof(QSPlayerLogTrade_Struct)+(sizeof(QSTradeItems_Struct)* event_entry._detail_count)); + QSPlayerLogTrade_Struct* qs_buf = (QSPlayerLogTrade_Struct*)qs_pack->pBuffer; + + memcpy(qs_buf, &event_entry, sizeof(QSPlayerLogTrade_Struct)); + + int offset = 0; + + for (std::list::iterator iter = event_details.begin(); iter != event_details.end(); ++iter, ++offset) { + QSTradeItems_Struct* detail = reinterpret_cast(*iter); + qs_buf->items[offset] = *detail; + safe_delete(detail); + } + + event_details.clear(); + + qs_pack->Deflate(); + + if(worldserver.Connected()) + worldserver.SendPacket(qs_pack); + + safe_delete(qs_pack); // end QS code } else { @@ -4926,25 +4940,43 @@ void Client::Handle_OP_TradeAcceptClick(const EQApplicationPacket *app) if(with->IsNPC()) { // Audit trade to database for player trade stream if(RuleB(QueryServ, PlayerLogHandins)) { - uint16 handin_count = 0; + QSPlayerLogHandin_Struct event_entry; + std::list event_details; - for(int16 slot_id = EmuConstants::TRADE_BEGIN; slot_id <= EmuConstants::TRADE_NPC_END; slot_id++) { - if(m_inv[slot_id]) { handin_count += m_inv[slot_id]->GetTotalItemCount(); } + memset(&event_entry, 0, sizeof(QSPlayerLogHandin_Struct)); + + FinishTrade(with->CastToNPC(), false, &event_entry, &event_details); + + event_entry._detail_count = event_details.size(); + + ServerPacket* qs_pack = new ServerPacket(ServerOP_QSPlayerLogHandins, sizeof(QSPlayerLogHandin_Struct)+(sizeof(QSHandinItems_Struct)* event_entry._detail_count)); + QSPlayerLogHandin_Struct* qs_buf = (QSPlayerLogHandin_Struct*)qs_pack->pBuffer; + + memcpy(qs_buf, &event_entry, sizeof(QSPlayerLogHandin_Struct)); + + int offset = 0; + + for (std::list::iterator iter = event_details.begin(); iter != event_details.end(); ++iter, ++offset) { + QSHandinItems_Struct* detail = reinterpret_cast(*iter); + qs_buf->items[offset] = *detail; + safe_delete(detail); } - ServerPacket* qspack = new ServerPacket(ServerOP_QSPlayerLogHandins, sizeof(QSPlayerLogHandin_Struct) + (sizeof(QSHandinItems_Struct) * handin_count)); + event_details.clear(); - FinishTrade(with->CastToNPC(), qspack); + qs_pack->Deflate(); - qspack->Deflate(); - if(worldserver.Connected()) { worldserver.SendPacket(qspack); } - safe_delete(qspack); + if(worldserver.Connected()) + worldserver.SendPacket(qs_pack); + + safe_delete(qs_pack); } else { FinishTrade(with->CastToNPC()); } } #ifdef BOTS + // TODO: Log Bot trades else if(with->IsBot()) with->CastToBot()->FinishTrade(this, Bot::BotTradeClientNormal); #endif @@ -5687,8 +5719,8 @@ void Client::Handle_OP_ShopPlayerBuy(const EQApplicationPacket *app) safe_delete(outapp); // start QS code - if(RuleB(QueryServ, MerchantLogTransactions)) { - ServerPacket* qspack = new ServerPacket(ServerOP_QSMerchantLogTransactions, sizeof(QSMerchantLogTransaction_Struct) + sizeof(QSTransactionItems_Struct)); + if(RuleB(QueryServ, PlayerLogMerchantTransactions)) { + ServerPacket* qspack = new ServerPacket(ServerOP_QSPlayerLogMerchantTransactions, sizeof(QSMerchantLogTransaction_Struct) + sizeof(QSTransactionItems_Struct)); QSMerchantLogTransaction_Struct* qsaudit = (QSMerchantLogTransaction_Struct*)qspack->pBuffer; qsaudit->zone_id = zone->GetZoneID(); @@ -5823,8 +5855,8 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app) } // start QS code - if(RuleB(QueryServ, MerchantLogTransactions)) { - ServerPacket* qspack = new ServerPacket(ServerOP_QSMerchantLogTransactions, sizeof(QSMerchantLogTransaction_Struct) + sizeof(QSTransactionItems_Struct)); + if(RuleB(QueryServ, PlayerLogMerchantTransactions)) { + ServerPacket* qspack = new ServerPacket(ServerOP_QSPlayerLogMerchantTransactions, sizeof(QSMerchantLogTransaction_Struct) + sizeof(QSTransactionItems_Struct)); QSMerchantLogTransaction_Struct* qsaudit = (QSMerchantLogTransaction_Struct*)qspack->pBuffer; qsaudit->zone_id = zone->GetZoneID(); @@ -5862,9 +5894,9 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app) else this->DeleteItemInInventory(mp->itemslot,mp->quantity,false); - //This forces the price to show up correctly for charged items. + //This forces the price to show up correctly for charged items. if(inst->IsCharged()) - mp->quantity = 1; + mp->quantity = 1; EQApplicationPacket* outapp = new EQApplicationPacket(OP_ShopPlayerSell, sizeof(Merchant_Purchase_Struct)); Merchant_Purchase_Struct* mco=(Merchant_Purchase_Struct*)outapp->pBuffer; @@ -5984,7 +6016,7 @@ void Client::Handle_OP_ClickObject(const EQApplicationPacket *app) object->HandleClick(this, click_object); - std::vector args; + std::vector args; args.push_back(object); char buf[10]; @@ -6220,7 +6252,7 @@ void Client::Handle_OP_ClickDoor(const EQApplicationPacket *app) char buf[20]; snprintf(buf, 19, "%u", cd->doorid); buf[19] = '\0'; - std::vector args; + std::vector args; args.push_back(currentdoor); parse->EventPlayer(EVENT_CLICK_DOOR, this, buf, 0, &args); @@ -7633,7 +7665,7 @@ void Client::Handle_OP_Mend(const EQApplicationPacket *app) int mendhp = GetMaxHP() / 4; int currenthp = GetHP(); if (MakeRandomInt(0, 199) < (int)GetSkill(SkillMend)) { - + int criticalchance = spellbonuses.CriticalMend + itembonuses.CriticalMend + aabonuses.CriticalMend; if(MakeRandomInt(0,99) < criticalchance){ @@ -9180,8 +9212,7 @@ bool Client::FinishConnState2(DBAsyncWork* dbaw) { if(!GetAA(aaPersistentMinion)) memset(&m_suspendedminion, 0, sizeof(PetInfo)); - //////////////////////////////////////////////////////////// - // Server Zone Entry Packet + /* Server Zone Entry Packet */ outapp = new EQApplicationPacket(OP_ZoneEntry, sizeof(ServerZoneEntry_Struct)); ServerZoneEntry_Struct* sze = (ServerZoneEntry_Struct*)outapp->pBuffer; @@ -9191,43 +9222,31 @@ bool Client::FinishConnState2(DBAsyncWork* dbaw) { sze->player.spawn.z += 6; //arbitrary lift, seems to help spawning under zone. outapp->priority = 6; FastQueuePacket(&outapp); - //safe_delete(outapp); - //////////////////////////////////////////////////////////// - // Zone Spawns Packet + /* Zone Spawns Packet */ entity_list.SendZoneSpawnsBulk(this); entity_list.SendZoneCorpsesBulk(this); entity_list.SendZonePVPUpdates(this); //hack until spawn struct is fixed. - - - //////////////////////////////////////////////////////////// - // Time of Day packet + /* Time of Day packet */ outapp = new EQApplicationPacket(OP_TimeOfDay, sizeof(TimeOfDay_Struct)); TimeOfDay_Struct* tod = (TimeOfDay_Struct*)outapp->pBuffer; zone->zone_time.getEQTimeOfDay(time(0), tod); outapp->priority = 6; FastQueuePacket(&outapp); - //safe_delete(outapp); - //I think this should happen earlier, not sure - /* if(GetHideMe()) - SetHideMe(true); */ - // Moved to Handle_Connect_OP_SendExpZonein(); - - - //////////////////////////////////////////////////////////// - // Tribute Packets + /* Tribute Packets */ DoTributeUpdate(); if(m_pp.tribute_active) { //restart the tribute timer where we left off tribute_timer.Start(m_pp.tribute_time_remaining); } - //////////////////////////////////////////////////////////// - // Character Inventory Packet - //this is not quite where live sends inventory, they do it after tribute - if (loaditems) {//dont load if a length error occurs + /* + Character Inventory Packet + this is not quite where live sends inventory, they do it after tribute + */ + if (loaditems) { //dont load if a length error occurs BulkSendInventoryItems(); // Send stuff on the cursor which isnt sent in bulk @@ -9241,9 +9260,7 @@ bool Client::FinishConnState2(DBAsyncWork* dbaw) { } } - - //////////////////////////////////////////////////////////// - // Task Packets + /* Task Packets */ LoadClientTaskState(); if (GetClientVersion() >= EQClientRoF) @@ -9261,10 +9278,11 @@ bool Client::FinishConnState2(DBAsyncWork* dbaw) { FastQueuePacket(&outapp); } - ////////////////////////////////////// - // Weather Packet - // This shouldent be moved, this seems to be what the client - // uses to advance to the next state (sending ReqNewZone) + /* + Weather Packet + This shouldent be moved, this seems to be what the client + uses to advance to the next state (sending ReqNewZone) + */ outapp = new EQApplicationPacket(OP_Weather, 12); Weather_Struct *ws = (Weather_Struct *) outapp->pBuffer; ws->val1 = 0x000000FF; @@ -9279,16 +9297,6 @@ bool Client::FinishConnState2(DBAsyncWork* dbaw) { QueuePacket(outapp); safe_delete(outapp); - ////////////////////////////////////// - // Group Roles - // - ////////////////////////////////////// - /*if(group){ - group->NotifyMainTank(this, 1); - group->NotifyMainAssist(this, 1); - group->NotifyPuller(this, 1); - }*/ - SetAttackTimer(); conn_state = ZoneInfoSent; @@ -9296,9 +9304,8 @@ bool Client::FinishConnState2(DBAsyncWork* dbaw) { return true; } -// Finish client connecting state -void Client::CompleteConnect() -{ +/* Finish client connecting state */ +void Client::CompleteConnect() { UpdateWho(); client_state = CLIENT_CONNECTED; @@ -9311,14 +9318,14 @@ void Client::CompleteConnect() EnteringMessages(this); LoadZoneFlags(); - // Sets GM Flag if needed & Sends Petition Queue + /* Sets GM Flag if needed & Sends Petition Queue */ UpdateAdmin(false); - if(IsInAGuild()){ + if (IsInAGuild()){ SendAppearancePacket(AT_GuildID, GuildID(), false); SendAppearancePacket(AT_GuildRank, GuildRank(), false); } - for(uint32 spellInt= 0; spellInt < MAX_PP_SPELLBOOK; spellInt++) + for (uint32 spellInt = 0; spellInt < MAX_PP_SPELLBOOK; spellInt++) { if (m_pp.spell_book[spellInt] < 3 || m_pp.spell_book[spellInt] > 50000) m_pp.spell_book[spellInt] = 0xFFFFFFFF; @@ -9329,35 +9336,37 @@ void Client::CompleteConnect() uint32 raidid = database.GetRaidID(GetName()); Raid *raid = nullptr; - if(raidid > 0){ + if (raidid > 0){ raid = entity_list.GetRaidByID(raidid); - if(!raid){ + if (!raid){ raid = new Raid(raidid); - if(raid->GetID() != 0){ + if (raid->GetID() != 0){ entity_list.AddRaid(raid, raidid); } else raid = nullptr; } - if(raid){ + if (raid){ SetRaidGrouped(true); raid->LearnMembers(); raid->VerifyRaid(); raid->GetRaidDetails(); - //only leader should get this; send to all for now till - //I figure out correct creation; can probably also send a no longer leader packet for non leaders - //but not important for now. + /* + Only leader should get this; send to all for now till + I figure out correct creation; can probably also send a no longer leader packet for non leaders + but not important for now. + */ raid->SendRaidCreate(this); raid->SendMakeLeaderPacketTo(raid->leadername, this); raid->SendRaidAdd(GetName(), this); raid->SendBulkRaid(this); raid->SendGroupUpdate(this); uint32 grpID = raid->GetGroup(GetName()); - if(grpID < 12){ + if (grpID < 12){ raid->SendRaidGroupRemove(GetName(), grpID); raid->SendRaidGroupAdd(GetName(), grpID); } - if(raid->IsLocked()) + if (raid->IsLocked()) raid->SendRaidLockTo(this); } } @@ -9366,154 +9375,155 @@ void Client::CompleteConnect() //reapply some buffs uint32 buff_count = GetMaxTotalSlots(); - for (uint32 j1=0; j1 < buff_count; j1++) { - if (buffs[j1].spellid > (uint32)SPDAT_RECORDS) + for (uint32 j1 = 0; j1 < buff_count; j1++) { + if (buffs[j1].spellid >(uint32)SPDAT_RECORDS) continue; const SPDat_Spell_Struct &spell = spells[buffs[j1].spellid]; - for (int x1=0; x1 < EFFECT_COUNT; x1++) { + for (int x1 = 0; x1 < EFFECT_COUNT; x1++) { switch (spell.effectid[x1]) { - case SE_IllusionCopy: - case SE_Illusion: { - if (spell.base[x1] == -1) { - if (gender == 1) - gender = 0; - else if (gender == 0) - gender = 1; - SendIllusionPacket(GetRace(), gender, 0xFF, 0xFF); - } - else if (spell.base[x1] == -2) - { - if (GetRace() == 128 || GetRace() == 130 || GetRace() <= 12) - SendIllusionPacket(GetRace(), GetGender(), spell.max[x1], spell.max[x1]); - } - else if (spell.max[x1] > 0) - { - SendIllusionPacket(spell.base[x1], 0xFF, spell.max[x1], spell.max[x1]); - } - else - { - SendIllusionPacket(spell.base[x1], 0xFF, 0xFF, 0xFF); - } - switch(spell.base[x1]){ - case OGRE: - SendAppearancePacket(AT_Size, 9); - break; - case TROLL: - SendAppearancePacket(AT_Size, 8); - break; - case VAHSHIR: - case BARBARIAN: - SendAppearancePacket(AT_Size, 7); - break; - case HALF_ELF: - case WOOD_ELF: - case DARK_ELF: - case FROGLOK: - SendAppearancePacket(AT_Size, 5); - break; - case DWARF: - SendAppearancePacket(AT_Size, 4); - break; - case HALFLING: - case GNOME: - SendAppearancePacket(AT_Size, 3); - break; - default: - SendAppearancePacket(AT_Size, 6); - break; - } + case SE_IllusionCopy: + case SE_Illusion: { + if (spell.base[x1] == -1) { + if (gender == 1) + gender = 0; + else if (gender == 0) + gender = 1; + SendIllusionPacket(GetRace(), gender, 0xFF, 0xFF); + } + else if (spell.base[x1] == -2) + { + if (GetRace() == 128 || GetRace() == 130 || GetRace() <= 12) + SendIllusionPacket(GetRace(), GetGender(), spell.max[x1], spell.max[x1]); + } + else if (spell.max[x1] > 0) + { + SendIllusionPacket(spell.base[x1], 0xFF, spell.max[x1], spell.max[x1]); + } + else + { + SendIllusionPacket(spell.base[x1], 0xFF, 0xFF, 0xFF); + } + switch (spell.base[x1]){ + case OGRE: + SendAppearancePacket(AT_Size, 9); + break; + case TROLL: + SendAppearancePacket(AT_Size, 8); + break; + case VAHSHIR: + case BARBARIAN: + SendAppearancePacket(AT_Size, 7); + break; + case HALF_ELF: + case WOOD_ELF: + case DARK_ELF: + case FROGLOK: + SendAppearancePacket(AT_Size, 5); + break; + case DWARF: + SendAppearancePacket(AT_Size, 4); + break; + case HALFLING: + case GNOME: + SendAppearancePacket(AT_Size, 3); + break; + default: + SendAppearancePacket(AT_Size, 6); break; } - case SE_SummonHorse: { - SummonHorse(buffs[j1].spellid); - //hasmount = true; //this was false, is that the correct thing? - break; + break; + } + case SE_SummonHorse: { + SummonHorse(buffs[j1].spellid); + //hasmount = true; //this was false, is that the correct thing? + break; + } + case SE_Silence: + { + Silence(true); + break; + } + case SE_Amnesia: + { + Amnesia(true); + break; + } + case SE_DivineAura: + { + invulnerable = true; + break; + } + case SE_Invisibility2: + case SE_Invisibility: + { + invisible = true; + SendAppearancePacket(AT_Invis, 1); + break; + } + case SE_Levitate: + { + if (!zone->CanLevitate()) + { + if (!GetGM()) + { + SendAppearancePacket(AT_Levitate, 0); + BuffFadeByEffect(SE_Levitate); + Message(13, "You can't levitate in this zone."); + } } - case SE_Silence: - { - Silence(true); - break; - } - case SE_Amnesia: - { - Amnesia(true); - break; - } - case SE_DivineAura: - { - invulnerable = true; - break; - } - case SE_Invisibility2: - case SE_Invisibility: - { - invisible = true; - SendAppearancePacket(AT_Invis, 1); - break; - } - case SE_Levitate: - { - if( !zone->CanLevitate() ) - { - if(!GetGM()) - { - SendAppearancePacket(AT_Levitate, 0); - BuffFadeByEffect(SE_Levitate); - Message(13, "You can't levitate in this zone."); - } - }else{ - SendAppearancePacket(AT_Levitate, 2); - } - break; - } - case SE_InvisVsUndead2: - case SE_InvisVsUndead: - { - invisible_undead = true; - break; - } - case SE_InvisVsAnimals: - { - invisible_animals = true; - break; - } - case SE_AddMeleeProc: - case SE_WeaponProc: - { - AddProcToWeapon(GetProcID(buffs[j1].spellid, x1), false, 100+spells[buffs[j1].spellid].base2[x1], buffs[j1].spellid); - break; - } - case SE_DefensiveProc: - { - AddDefensiveProc(GetProcID(buffs[j1].spellid, x1), 100+spells[buffs[j1].spellid].base2[x1],buffs[j1].spellid); - break; - } - case SE_RangedProc: - { - AddRangedProc(GetProcID(buffs[j1].spellid, x1), 100+spells[buffs[j1].spellid].base2[x1],buffs[j1].spellid); - break; - } + else{ + SendAppearancePacket(AT_Levitate, 2); + } + break; + } + case SE_InvisVsUndead2: + case SE_InvisVsUndead: + { + invisible_undead = true; + break; + } + case SE_InvisVsAnimals: + { + invisible_animals = true; + break; + } + case SE_AddMeleeProc: + case SE_WeaponProc: + { + AddProcToWeapon(GetProcID(buffs[j1].spellid, x1), false, 100 + spells[buffs[j1].spellid].base2[x1], buffs[j1].spellid); + break; + } + case SE_DefensiveProc: + { + AddDefensiveProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].base2[x1], buffs[j1].spellid); + break; + } + case SE_RangedProc: + { + AddRangedProc(GetProcID(buffs[j1].spellid, x1), 100 + spells[buffs[j1].spellid].base2[x1], buffs[j1].spellid); + break; + } } } } - //sends appearances for all mobs not doing anim_stand aka sitting, looting, playing dead + /* Sends appearances for all mobs not doing anim_stand aka sitting, looting, playing dead */ entity_list.SendZoneAppearance(this); - //sends the Nimbus particle effects (up to 3) for any mob using them + /* Sends the Nimbus particle effects (up to 3) for any mob using them */ entity_list.SendNimbusEffects(this); entity_list.SendUntargetable(this); client_data_loaded = true; int x; - for(x=0;x<8;x++) + for (x = 0; x < 8; x++) SendWearChange(x); Mob *pet = GetPet(); - if(pet != nullptr) { - for(x=0;x<8;x++) + if (pet != nullptr) { + for (x = 0; x < 8; x++) pet->SendWearChange(x); } @@ -9521,14 +9531,14 @@ void Client::CompleteConnect() zoneinpacket_timer.Start(); - if(GetPet()){ + if (GetPet()){ GetPet()->SendPetBuffsToClient(); } - if(GetGroup()) + if (GetGroup()) database.RefreshGroupFromDB(this); - if(RuleB(TaskSystem, EnableTaskSystem)) + if (RuleB(TaskSystem, EnableTaskSystem)) TaskPeriodic_Timer.Start(); else TaskPeriodic_Timer.Disable(); @@ -9536,51 +9546,50 @@ void Client::CompleteConnect() conn_state = ClientConnectFinished; //enforce some rules.. - if(!CanBeInZone()) { + if (!CanBeInZone()) { _log(CLIENT__ERROR, "Kicking char from zone, not allowed here"); GoToSafeCoords(database.GetZoneID("arena"), 0); return; } - if(zone) + if (zone) zone->weatherSend(); TotalKarma = database.GetKarma(AccountID()); - SendDisciplineTimers(); parse->EventPlayer(EVENT_ENTER_ZONE, this, "", 0); - //This sub event is for if a player logs in for the first time since entering world. - if(firstlogon == 1) + /* This sub event is for if a player logs in for the first time since entering world. */ + if (firstlogon == 1){ parse->EventPlayer(EVENT_CONNECT, this, "", 0); + /* QS: PlayerLogConnectDisconnect */ + if (RuleB(QueryServ, PlayerLogConnectDisconnect)){ + std::string event_desc = StringFormat("Connect :: Logged into zoneid:%i instid:%i", this->GetZoneID(), this->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_Connect_State, this->CharacterID(), event_desc); + } + } - if(zone) - { - if(zone->GetInstanceTimer()) - { + if(zone) { + if(zone->GetInstanceTimer()) { uint32 ttime = zone->GetInstanceTimer()->GetRemainingTime(); uint32 day = (ttime/86400000); uint32 hour = (ttime/3600000)%24; uint32 minute = (ttime/60000)%60; uint32 second = (ttime/1000)%60; - if(day) - { + if(day) { Message(15, "%s(%u) will expire in %u days, %u hours, %u minutes, and %u seconds.", zone->GetLongName(), zone->GetInstanceID(), day, hour, minute, second); } - else if(hour) - { + else if(hour) { Message(15, "%s(%u) will expire in %u hours, %u minutes, and %u seconds.", zone->GetLongName(), zone->GetInstanceID(), hour, minute, second); } - else if(minute) - { + else if(minute) { Message(15, "%s(%u) will expire in %u minutes, and %u seconds.", zone->GetLongName(), zone->GetInstanceID(), minute, second); } - else - { + else { Message(15, "%s(%u) will expire in in %u seconds.", zone->GetLongName(), zone->GetInstanceID(), second); } @@ -9603,8 +9612,7 @@ void Client::CompleteConnect() if(GetClientVersion() >= EQClientSoD) entity_list.SendFindableNPCList(this); - if(IsInAGuild()) - { + if(IsInAGuild()) { SendGuildRanks(); guild_mgr.SendGuildMemberUpdateToWorld(GetName(), GuildID(), zone->GetZoneID(), time(nullptr)); guild_mgr.RequestOnlineGuildMembers(this->CharacterID(), this->GuildID()); @@ -9616,8 +9624,7 @@ void Client::CompleteConnect() worldserver.SendPacket(pack); delete pack; - if(IsClient() && CastToClient()->GetClientVersionBit() & BIT_UnderfootAndLater) - { + if(IsClient() && CastToClient()->GetClientVersionBit() & BIT_UnderfootAndLater) { EQApplicationPacket *outapp = MakeBuffsPacket(false); CastToClient()->FastQueuePacket(&outapp); } @@ -11433,92 +11440,68 @@ void Client::Handle_OP_SetStartCity(const EQApplicationPacket *app) Message(15,"Your home city has already been set.", m_pp.binds[4].zoneId, database.GetZoneName(m_pp.binds[4].zoneId)); return; } + if (app->size < 1) { LogFile->write(EQEMuLog::Error, "Wrong size: OP_SetStartCity, size=%i, expected %i", app->size, 1); DumpPacket(app); return; } - char errbuf[MYSQL_ERRMSG_SIZE]; - char *query = 0; - MYSQL_RES *result = nullptr; - MYSQL_ROW row = 0; float x(0),y(0),z(0); uint32 zoneid = 0; + uint32 startCity = (uint32)strtol((const char*)app->pBuffer, nullptr, 10); - uint32 StartCity = (uint32)strtol((const char*)app->pBuffer, nullptr, 10); - bool ValidCity = false; - database.RunQuery - ( - query, - MakeAnyLenString - ( - &query, - "SELECT zone_id, bind_id, x, y, z FROM start_zones " - "WHERE player_class=%i AND player_deity=%i AND player_race=%i", - m_pp.class_, - m_pp.deity, - m_pp.race - ), - errbuf, - &result - ); - safe_delete_array(query); - - if(!result) { + std::string query = StringFormat("SELECT zone_id, bind_id, x, y, z FROM start_zones " + "WHERE player_class=%i AND player_deity=%i AND player_race=%i", + m_pp.class_, m_pp.deity, m_pp.race); + auto results = database.QueryDatabase(query); + if(!results.Success()) { LogFile->write(EQEMuLog::Error, "No valid start zones found for /setstartcity"); return; } - while(row = mysql_fetch_row(result)) { + bool validCity = false; + for (auto row = results.begin(); row != results.end(); ++row) { if(atoi(row[1]) != 0) zoneid = atoi(row[1]); else zoneid = atoi(row[0]); - if(zoneid == StartCity) { - ValidCity = true; - x = atof(row[2]); - y = atof(row[3]); - z = atof(row[4]); - } + if(zoneid != startCity) + continue; + + validCity = true; + x = atof(row[2]); + y = atof(row[3]); + z = atof(row[4]); } - if(ValidCity) { + if(validCity) { Message(15,"Your home city has been set"); - SetStartZone(StartCity, x, y, z); - } - else { - database.RunQuery - ( - query, - MakeAnyLenString - ( - &query, - "SELECT zone_id, bind_id FROM start_zones " - "WHERE player_class=%i AND player_deity=%i AND player_race=%i", - m_pp.class_, - m_pp.deity, - m_pp.race - ), - errbuf, - &result - ); - safe_delete_array(query); - Message(15,"Use \"/startcity #\" to choose a home city from the following list:"); - char* name; - while(row = mysql_fetch_row(result)) { - if(atoi(row[1]) != 0) - zoneid = atoi(row[1]); - else - zoneid = atoi(row[0]); - database.GetZoneLongName(database.GetZoneName(zoneid),&name); - Message(15,"%d - %s", zoneid, name); - safe_delete_array(name); - } + SetStartZone(startCity, x, y, z); + return; } - mysql_free_result(result); + query = StringFormat("SELECT zone_id, bind_id FROM start_zones " + "WHERE player_class=%i AND player_deity=%i AND player_race=%i", + m_pp.class_, m_pp.deity, m_pp.race); + results = database.QueryDatabase(query); + if (!results.Success()) + return; + + Message(15,"Use \"/startcity #\" to choose a home city from the following list:"); + + for (auto row = results.begin(); row != results.end(); ++row) { + if(atoi(row[1]) != 0) + zoneid = atoi(row[1]); + else + zoneid = atoi(row[0]); + + char* name; + database.GetZoneLongName(database.GetZoneName(zoneid), &name); + Message(15,"%d - %s", zoneid, name); + } + } void Client::Handle_OP_Report(const EQApplicationPacket *app) @@ -11625,7 +11608,7 @@ void Client::Handle_OP_GMSearchCorpse(const EQApplicationPacket *app) // Could make this into a rule, although there is a hard limit since we are using a popup, of 4096 bytes that can // be displayed in the window, including all the HTML formatting tags. // - const int MaxResults = 10; + const int maxResults = 10; if(app->size < sizeof(GMSearchCorpse_Struct)) { @@ -11638,85 +11621,62 @@ void Client::Handle_OP_GMSearchCorpse(const EQApplicationPacket *app) GMSearchCorpse_Struct *gmscs = (GMSearchCorpse_Struct *)app->pBuffer; gmscs->Name[63] = '\0'; - char errbuf[MYSQL_ERRMSG_SIZE]; - char* Query = 0; - MYSQL_RES *Result; - MYSQL_ROW Row; + char *escSearchString = new char[129]; + database.DoEscapeString(escSearchString, gmscs->Name, strlen(gmscs->Name)); - char *EscSearchString = new char[129]; + std::string query = StringFormat("SELECT charname, zoneid, x, y, z, timeofdeath, rezzed, IsBurried " + "FROM player_corpses WheRE charname LIKE '%%%s%%' ORDER BY charname LIMIT %i", + escSearchString, maxResults); + safe_delete_array(escSearchString); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + Message(0, "Query failed: %s.", results.ErrorMessage().c_str()); + return; + } - database.DoEscapeString(EscSearchString, gmscs->Name, strlen(gmscs->Name)); + if (results.RowCount() == 0) + return; - if (database.RunQuery(Query, MakeAnyLenString(&Query, "select charname, zoneid, x, y, z, timeofdeath, rezzed, IsBurried from " - "player_corpses where charname like '%%%s%%' order by charname limit %i", - EscSearchString, MaxResults), errbuf, &Result)) - { + if(results.RowCount() == maxResults) + Message(clientMessageError, "Your search found too many results; some are not displayed."); + else + Message(clientMessageYellow, "There are %i corpse(s) that match the search string '%s'.", results.RowCount(), gmscs->Name); - int NumberOfRows = mysql_num_rows(Result); + char charName[64], timeOfDeath[20]; - if(NumberOfRows == MaxResults) - Message(clientMessageError, "Your search found too many results; some are not displayed."); - else { - Message(clientMessageYellow, "There are %i corpse(s) that match the search string '%s'.", - NumberOfRows, gmscs->Name); - } - - if(NumberOfRows == 0) - { - mysql_free_result(Result); - safe_delete_array(Query); - return; - } - - char CharName[64], TimeOfDeath[20], Buffer[512]; - - std::string PopupText = "
NameZoneXYZDate" + std::string popupText = ""; + for (auto row = results.begin(); row != results.end(); ++row) { - while ((Row = mysql_fetch_row(Result))) - { + strn0cpy(charName, row[0], sizeof(charName)); - strn0cpy(CharName, Row[0], sizeof(CharName)); + uint32 ZoneID = atoi(row[1]); + float CorpseX = atof(row[2]); + float CorpseY = atof(row[3]); + float CorpseZ = atof(row[4]); - uint32 ZoneID = atoi(Row[1]); + strn0cpy(timeOfDeath, row[5], sizeof(timeOfDeath)); - float CorpseX = atof(Row[2]); - float CorpseY = atof(Row[3]); - float CorpseZ = atof(Row[4]); + bool corpseRezzed = atoi(row[6]); + bool corpseBuried = atoi(row[7]); - strn0cpy(TimeOfDeath, Row[5], sizeof(TimeOfDeath)); + popupText += StringFormat("", + charName, StaticGetZoneName(ZoneID), CorpseX, CorpseY, CorpseZ, timeOfDeath, + corpseRezzed ? "Yes" : "No", corpseBuried ? "Yes" : "No"); - bool CorpseRezzed = atoi(Row[6]); - bool CorpseBuried = atoi(Row[7]); + if(popupText.size() > 4000) { + Message(clientMessageError, "Unable to display all the results."); + break; + } - sprintf(Buffer, "", - CharName, StaticGetZoneName(ZoneID), CorpseX, CorpseY, CorpseZ, TimeOfDeath, - CorpseRezzed ? "Yes" : "No", CorpseBuried ? "Yes" : "No"); + } - PopupText += Buffer; + popupText += "
NameZoneXYZDate" "RezzedBuried
 " "
%s%s%8.0f%8.0f%8.0f%s%s%s
%s%s%8.0f%8.0f%8.0f%s%s%s
"; - if(PopupText.size() > 4000) - { - Message(clientMessageError, "Unable to display all the results."); - break; - } + SendPopupToClient("Corpses", popupText.c_str()); - } - - PopupText += "
"; - - mysql_free_result(Result); - - SendPopupToClient("Corpses", PopupText.c_str()); - } - else{ - Message(0, "Query failed: %s.", errbuf); - - } - safe_delete_array(Query); - safe_delete_array(EscSearchString); } void Client::Handle_OP_GuildBank(const EQApplicationPacket *app) @@ -12749,6 +12709,12 @@ void Client::Handle_OP_AltCurrencyPurchase(const EQApplicationPacket *app) { return; } + /* QS: PlayerLogAlternateCurrencyTransactions :: Merchant Purchase */ + if (RuleB(QueryServ, PlayerLogAlternateCurrencyTransactions)){ + std::string event_desc = StringFormat("Merchant Purchase :: Spent alt_currency_id:%i cost:%i for itemid:%i in zoneid:%i instid:%i", alt_cur_id, cost, item->ID, this->GetZoneID(), this->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_Alternate_Currency_Transactions, this->CharacterID(), event_desc); + } + AddAlternateCurrencyValue(alt_cur_id, -((int32)cost)); int16 charges = 1; if(item->MaxCharges != 0) @@ -12780,20 +12746,37 @@ void Client::Handle_OP_AltCurrencyReclaim(const EQApplicationPacket *app) { return; } - if(reclaim->reclaim_flag == 1) { //item -> altcur + /* Item to Currency Storage */ + if(reclaim->reclaim_flag == 1) { uint32 removed = NukeItem(item_id, invWhereWorn | invWherePersonal | invWhereCursor); if(removed > 0) { AddAlternateCurrencyValue(reclaim->currency_id, removed); + + /* QS: PlayerLogAlternateCurrencyTransactions :: Item to Currency */ + if (RuleB(QueryServ, PlayerLogAlternateCurrencyTransactions)){ + std::string event_desc = StringFormat("Reclaim :: Item to Currency :: alt_currency_id:%i amount:%i to currency tab in zoneid:%i instid:%i", reclaim->currency_id, removed, this->GetZoneID(), this->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_Alternate_Currency_Transactions, this->CharacterID(), event_desc); + } } - } else { + } + /* Cursor to Item storage */ + else { uint32 max_currency = GetAlternateCurrencyValue(reclaim->currency_id); + + /* If you input more than you have currency wise, just give the max of the currency you currently have */ if(reclaim->count > max_currency) { SummonItem(item_id, max_currency); SetAlternateCurrencyValue(reclaim->currency_id, 0); - } else { + } + else { SummonItem(item_id, reclaim->count, 0, 0, 0, 0, 0, false, MainCursor); AddAlternateCurrencyValue(reclaim->currency_id, -((int32)reclaim->count)); } + /* QS: PlayerLogAlternateCurrencyTransactions :: Cursor to Item Storage */ + if (RuleB(QueryServ, PlayerLogAlternateCurrencyTransactions)){ + std::string event_desc = StringFormat("Reclaim :: Cursor to Item :: alt_currency_id:%i amount:-%i in zoneid:%i instid:%i", reclaim->currency_id, reclaim->count, this->GetZoneID(), this->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_Alternate_Currency_Transactions, this->CharacterID(), event_desc); + } } } @@ -12829,6 +12812,7 @@ void Client::Handle_OP_AltCurrencySell(const EQApplicationPacket *app) { uint32 cost = 0; uint32 current_currency = GetAlternateCurrencyValue(alt_cur_id); uint32 merchant_id = tar->MerchantType; + uint32 npc_id = tar->GetNPCTypeID(); bool found = false; std::list merlist = zone->merchanttable[merchant_id]; std::list::const_iterator itr; @@ -12881,6 +12865,12 @@ void Client::Handle_OP_AltCurrencySell(const EQApplicationPacket *app) { sell->cost = cost; + /* QS: PlayerLogAlternateCurrencyTransactions :: Sold to Merchant*/ + if (RuleB(QueryServ, PlayerLogAlternateCurrencyTransactions)){ + std::string event_desc = StringFormat("Sold to Merchant :: itemid:%u npcid:%u alt_currency_id:%u cost:%u in zoneid:%u instid:%i", item->ID, npc_id, alt_cur_id, cost, this->GetZoneID(), this->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_Alternate_Currency_Transactions, this->CharacterID(), event_desc); + } + FastQueuePacket(&outapp); AddAlternateCurrencyValue(alt_cur_id, cost); Save(1); diff --git a/zone/client_process.cpp b/zone/client_process.cpp index 3fea686ab..e39cef0bd 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -63,7 +63,9 @@ #include "guild_mgr.h" #include #include "quest_parser_collection.h" +#include "queryserv.h" +extern QueryServ* QServ; extern Zone* zone; extern volatile bool ZoneLoaded; extern WorldServer worldserver; @@ -770,38 +772,40 @@ bool Client::Process() { return ret; } -//just a set of actions preformed all over in Client::Process +/* Just a set of actions preformed all over in Client::Process */ void Client::OnDisconnect(bool hard_disconnect) { if(hard_disconnect) { - LeaveGroup(); - + LeaveGroup(); Raid *MyRaid = entity_list.GetRaidByClient(this); if (MyRaid) MyRaid->MemberZoned(this); - parse->EventPlayer(EVENT_DISCONNECT, this, "", 0); + parse->EventPlayer(EVENT_DISCONNECT, this, "", 0); + + /* QS: PlayerLogConnectDisconnect */ + if (RuleB(QueryServ, PlayerLogConnectDisconnect)){ + std::string event_desc = StringFormat("Disconnect :: in zoneid:%i instid:%i", this->GetZoneID(), this->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_Connect_State, this->CharacterID(), event_desc); + } } - Mob *Other = trade->With(); - - if(Other) - { - mlog(TRADING__CLIENT, "Client disconnected during a trade. Returning their items."); - + Mob *Other = trade->With(); + if(Other) { + mlog(TRADING__CLIENT, "Client disconnected during a trade. Returning their items."); FinishTrade(this); if(Other->IsClient()) Other->CastToClient()->FinishTrade(Other); - trade->Reset(); - + /* Reset both sides of the trade */ + trade->Reset(); Other->trade->Reset(); } database.SetFirstLogon(CharacterID(), 0); //We change firstlogon status regardless of if a player logs out to zone or not, because we only want to trigger it on their first login from world. - //remove ourself from all proximities + /* Remove ourself from all proximities */ ClearAllProximities(); EQApplicationPacket *outapp = new EQApplicationPacket(OP_LogoutReply); diff --git a/zone/command.cpp b/zone/command.cpp index 555804cfa..b14a173bc 100644 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -62,8 +62,9 @@ #include "guild_mgr.h" #include "titles.h" #include "../common/patches/patches.h" +#include "queryserv.h" -// these should be in the headers... +extern QueryServ* QServ; extern WorldServer worldserver; extern TaskManager *taskmanager; void CatchSignal(int sig_num); @@ -588,6 +589,12 @@ int command_realdispatch(Client *c, const char *message) return(-1); } + /* QS: Player_Log_Issued_Commands */ + if (RuleB(QueryServ, PlayerLogIssuedCommandes)){ + std::string event_desc = StringFormat("Issued command :: '%s' in zoneid:%i instid:%i", message, c->GetZoneID(), c->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_Issued_Commands, c->CharacterID(), event_desc); + } + #ifdef COMMANDS_LOGGING if(cur->access >= COMMANDS_LOGGING_MIN_STATUS) { LogFile->write(EQEMuLog::Commands, "%s (%s) used command: %s (target=%s)", c->GetName(), c->AccountName(), message, c->GetTarget()?c->GetTarget()->GetName():"NONE"); diff --git a/zone/common.h b/zone/common.h index 3cc1af1cf..1401af860 100644 --- a/zone/common.h +++ b/zone/common.h @@ -127,7 +127,8 @@ enum { FLEE_PERCENT = 37, ALLOW_BENEFICIAL = 38, DISABLE_MELEE = 39, - MAX_SPECIAL_ATTACK = 40 + NPC_CHASE_DISTANCE = 40, + MAX_SPECIAL_ATTACK = 41 }; @@ -526,7 +527,7 @@ public: Mob* With(); // Add item from cursor slot to trade bucket (automatically does bag data too) - void AddEntity(uint16 from_slot_id, uint16 trade_slot_id, uint32 stack_size); + void AddEntity(uint16 trade_slot_id, uint32 stack_size); // Audit trade void LogTrade(); diff --git a/zone/corpse.cpp b/zone/corpse.cpp index aa7841a64..da9ffab17 100644 --- a/zone/corpse.cpp +++ b/zone/corpse.cpp @@ -1164,7 +1164,7 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) strcpy(corpse_name, orgname); snprintf(buf, 87, "%d %d %s", inst->GetItem()->ID, inst->GetCharges(), EntityList::RemoveNumbers(corpse_name)); buf[87] = '\0'; - std::vector args; + std::vector args; args.push_back(inst); args.push_back(this); parse->EventPlayer(EVENT_LOOT, client, buf, 0, &args); diff --git a/zone/doors.cpp b/zone/doors.cpp index c353ce568..5ff7eae26 100644 --- a/zone/doors.cpp +++ b/zone/doors.cpp @@ -568,157 +568,137 @@ void Doors::DumpDoor(){ } int32 ZoneDatabase::GetDoorsCount(uint32* oMaxID, const char *zone_name, int16 version) { - char errbuf[MYSQL_ERRMSG_SIZE]; - char *query = 0; - MYSQL_RES *result; - MYSQL_ROW row; - query = new char[256]; - sprintf(query, "SELECT MAX(id), count(*) FROM doors WHERE zone='%s' AND (version=%u OR version=-1)", zone_name, version); - if (RunQuery(query, strlen(query), errbuf, &result)) { - safe_delete_array(query); - row = mysql_fetch_row(result); - if (row != nullptr && row[1] != 0) { - int32 ret = atoi(row[1]); - if (oMaxID) { - if (row[0]) - *oMaxID = atoi(row[0]); - else - *oMaxID = 0; - } - mysql_free_result(result); - return ret; - } - mysql_free_result(result); - } - else { - std::cerr << "Error in GetDoorsCount query '" << query << "' " << errbuf << std::endl; - safe_delete_array(query); + std::string query = StringFormat("SELECT MAX(id), count(*) FROM doors " + "WHERE zone = '%s' AND (version = %u OR version = -1)", + zone_name, version); + auto results = QueryDatabase(query); + if (!results.Success()) { + std::cerr << "Error in GetDoorsCount query '" << query << "' " << results.ErrorMessage() << std::endl; return -1; - } + } + + if (results.RowCount() != 1) + return -1; + + auto row = results.begin(); + + if (!oMaxID) + return atoi(row[1]); + + if (row[0]) + *oMaxID = atoi(row[0]); + else + *oMaxID = 0; + + return atoi(row[1]); - return -1; } int32 ZoneDatabase::GetDoorsCountPlusOne(const char *zone_name, int16 version) { - char errbuf[MYSQL_ERRMSG_SIZE]; - char *query = 0; - uint32 oMaxID = 0; - MYSQL_RES *result; - MYSQL_ROW row; - query = new char[256]; - sprintf(query, "SELECT MAX(id) FROM doors WHERE zone='%s' AND version=%u", zone_name, version); - if (RunQuery(query, strlen(query), errbuf, &result)) { - safe_delete_array(query); - row = mysql_fetch_row(result); - if (row != nullptr && row[1] != 0) { - if (row[0]) - oMaxID = atoi(row[0]) + 1; - else - oMaxID = 0; - mysql_free_result(result); - return oMaxID; - } - mysql_free_result(result); - } - else { - std::cerr << "Error in GetDoorsCountPlusOne query '" << query << "' " << errbuf << std::endl; - safe_delete_array(query); + std::string query = StringFormat("SELECT MAX(id) FROM doors " + "WHERE zone = '%s' AND version = %u", zone_name, version); + auto results = QueryDatabase(query); + if (!results.Success()) { + std::cerr << "Error in GetDoorsCountPlusOne query '" << query << "' " << results.ErrorMessage() << std::endl; return -1; - } + } - return -1; + if (results.RowCount() != 1) + return -1; + + auto row = results.begin(); + + if (!row[0]) + return 0; + + return atoi(row[0]) + 1; } int32 ZoneDatabase::GetDoorsDBCountPlusOne(const char *zone_name, int16 version) { - char errbuf[MYSQL_ERRMSG_SIZE]; - char *query = 0; + uint32 oMaxID = 0; - MYSQL_RES *result; - MYSQL_ROW row; - query = new char[256]; - sprintf(query, "SELECT MAX(doorid) FROM doors WHERE zone='%s' AND (version=%u OR version=-1)", zone_name, version); - if (RunQuery(query, strlen(query), errbuf, &result)) { - safe_delete_array(query); - row = mysql_fetch_row(result); - if (row != nullptr && row[1] != 0) { - if (row[0]) - oMaxID = atoi(row[0]) + 1; - else - oMaxID = 0; - mysql_free_result(result); - return oMaxID; - } - mysql_free_result(result); - } - else { - std::cerr << "Error in GetDoorsCountPlusOne query '" << query << "' " << errbuf << std::endl; - safe_delete_array(query); + std::string query = StringFormat("SELECT MAX(doorid) FROM doors " + "WHERE zone = '%s' AND (version = %u OR version = -1)", + zone_name, version); + auto results = QueryDatabase(query); + if (!results.Success()) { + std::cerr << "Error in GetDoorsCountPlusOne query '" << query << "' " << results.ErrorMessage() << std::endl; return -1; } - return -1; + if (results.RowCount() != 1) + return -1; + + auto row = results.begin(); + + if (!row[0]) + return 0; + + return atoi(row[0]) + 1; } bool ZoneDatabase::LoadDoors(int32 iDoorCount, Door *into, const char *zone_name, int16 version) { LogFile->write(EQEMuLog::Status, "Loading Doors from database..."); - char errbuf[MYSQL_ERRMSG_SIZE]; - char *query = 0; - MYSQL_RES *result; - MYSQL_ROW row; + // Door tmpDoor; - MakeAnyLenString(&query, "SELECT id,doorid,zone,name,pos_x,pos_y,pos_z,heading," - "opentype,guild,lockpick,keyitem,nokeyring,triggerdoor,triggertype,dest_zone,dest_instance,dest_x," - "dest_y,dest_z,dest_heading,door_param,invert_state,incline,size,is_ldon_door,client_version_mask " - "FROM doors WHERE zone='%s' AND (version=%u OR version=-1) ORDER BY doorid asc", zone_name, version); - if (RunQuery(query, strlen(query), errbuf, &result)) { - safe_delete_array(query); - int32 r; - for(r = 0; (row = mysql_fetch_row(result)); r++) { - if(r >= iDoorCount) { - std::cerr << "Error, Door Count of " << iDoorCount << " exceeded." << std::endl; - break; - } - memset(&into[r], 0, sizeof(Door)); - into[r].db_id = atoi(row[0]); - into[r].door_id = atoi(row[1]); - strn0cpy(into[r].zone_name,row[2],32); - strn0cpy(into[r].door_name,row[3],32); - into[r].pos_x = (float)atof(row[4]); - into[r].pos_y = (float)atof(row[5]); - into[r].pos_z = (float)atof(row[6]); - into[r].heading = (float)atof(row[7]); - into[r].opentype = atoi(row[8]); - into[r].guild_id = atoi(row[9]); - into[r].lockpick = atoi(row[10]); - into[r].keyitem = atoi(row[11]); - into[r].nokeyring = atoi(row[12]); - into[r].trigger_door = atoi(row[13]); - into[r].trigger_type = atoi(row[14]); - strn0cpy(into[r].dest_zone, row[15], 32); - into[r].dest_instance_id = atoi(row[16]); - into[r].dest_x = (float) atof(row[17]); - into[r].dest_y = (float) atof(row[18]); - into[r].dest_z = (float) atof(row[19]); - into[r].dest_heading = (float) atof(row[20]); - into[r].door_param=atoi(row[21]); - into[r].invert_state=atoi(row[22]); - into[r].incline=atoi(row[23]); - into[r].size=atoi(row[24]); - into[r].is_ldon_door=atoi(row[25]); - into[r].client_version_mask = (uint32)strtoul(row[26], nullptr, 10); - } - mysql_free_result(result); - } - else - { - std::cerr << "Error in DBLoadDoors query '" << query << "' " << errbuf << std::endl; - safe_delete_array(query); + std::string query = StringFormat("SELECT id, doorid, zone, name, pos_x, pos_y, pos_z, heading, " + "opentype, guild, lockpick, keyitem, nokeyring, triggerdoor, triggertype, " + "dest_zone, dest_instance, dest_x, dest_y, dest_z, dest_heading, " + "door_param, invert_state, incline, size, is_ldon_door, client_version_mask " + "FROM doors WHERE zone = '%s' AND (version = %u OR version = -1) " + "ORDER BY doorid asc", zone_name, version); + auto results = QueryDatabase(query); + if (!results.Success()){ + std::cerr << "Error in DBLoadDoors query '" << query << "' " << results.ErrorMessage() << std::endl; return false; } + + int32 rowIndex = 0; + for(auto row = results.begin(); row != results.end(); ++row, ++rowIndex) { + if(rowIndex >= iDoorCount) { + std::cerr << "Error, Door Count of " << iDoorCount << " exceeded." << std::endl; + break; + } + + memset(&into[rowIndex], 0, sizeof(Door)); + + into[rowIndex].db_id = atoi(row[0]); + into[rowIndex].door_id = atoi(row[1]); + + strn0cpy(into[rowIndex].zone_name,row[2],32); + strn0cpy(into[rowIndex].door_name,row[3],32); + + into[rowIndex].pos_x = (float)atof(row[4]); + into[rowIndex].pos_y = (float)atof(row[5]); + into[rowIndex].pos_z = (float)atof(row[6]); + into[rowIndex].heading = (float)atof(row[7]); + into[rowIndex].opentype = atoi(row[8]); + into[rowIndex].guild_id = atoi(row[9]); + into[rowIndex].lockpick = atoi(row[10]); + into[rowIndex].keyitem = atoi(row[11]); + into[rowIndex].nokeyring = atoi(row[12]); + into[rowIndex].trigger_door = atoi(row[13]); + into[rowIndex].trigger_type = atoi(row[14]); + + strn0cpy(into[rowIndex].dest_zone, row[15], 32); + + into[rowIndex].dest_instance_id = atoi(row[16]); + into[rowIndex].dest_x = (float) atof(row[17]); + into[rowIndex].dest_y = (float) atof(row[18]); + into[rowIndex].dest_z = (float) atof(row[19]); + into[rowIndex].dest_heading = (float) atof(row[20]); + into[rowIndex].door_param=atoi(row[21]); + into[rowIndex].invert_state=atoi(row[22]); + into[rowIndex].incline=atoi(row[23]); + into[rowIndex].size=atoi(row[24]); + into[rowIndex].is_ldon_door=atoi(row[25]); + into[rowIndex].client_version_mask = (uint32)strtoul(row[26], nullptr, 10); + } + return true; } diff --git a/zone/embparser.cpp b/zone/embparser.cpp index 8c4a82f55..f577bcb11 100644 --- a/zone/embparser.cpp +++ b/zone/embparser.cpp @@ -154,7 +154,7 @@ void PerlembParser::ReloadQuests() { } int PerlembParser::EventCommon(QuestEventID event, uint32 objid, const char * data, NPC* npcmob, ItemInst* iteminst, Mob* mob, - uint32 extradata, bool global, std::vector *extra_pointers) + uint32 extradata, bool global, std::vector *extra_pointers) { if(!perl) return 0; @@ -211,32 +211,32 @@ int PerlembParser::EventCommon(QuestEventID event, uint32 objid, const char * da } int PerlembParser::EventNPC(QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { return EventCommon(evt, npc->GetNPCTypeID(), data.c_str(), npc, nullptr, init, extra_data, false, extra_pointers); } int PerlembParser::EventGlobalNPC(QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { return EventCommon(evt, npc->GetNPCTypeID(), data.c_str(), npc, nullptr, init, extra_data, true, extra_pointers); } int PerlembParser::EventPlayer(QuestEventID evt, Client *client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { return EventCommon(evt, 0, data.c_str(), nullptr, nullptr, client, extra_data, false, extra_pointers); } int PerlembParser::EventGlobalPlayer(QuestEventID evt, Client *client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { return EventCommon(evt, 0, data.c_str(), nullptr, nullptr, client, extra_data, true, extra_pointers); } int PerlembParser::EventItem(QuestEventID evt, Client *client, ItemInst *item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { return EventCommon(evt, item->GetID(), nullptr, nullptr, item, client, extra_data, false, extra_pointers); } int PerlembParser::EventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { return EventCommon(evt, 0, itoa(spell_id), npc, nullptr, client, extra_data, false, extra_pointers); } @@ -1114,7 +1114,7 @@ void PerlembParser::ExportItemVariables(std::string &package_name, Mob *mob) { #undef HASITEM_ISNULLITEM void PerlembParser::ExportEventVariables(std::string &package_name, QuestEventID event, uint32 objid, const char * data, - NPC* npcmob, ItemInst* iteminst, Mob* mob, uint32 extradata, std::vector *extra_pointers) + NPC* npcmob, ItemInst* iteminst, Mob* mob, uint32 extradata, std::vector *extra_pointers) { switch (event) { case EVENT_SAY: { @@ -1131,7 +1131,7 @@ void PerlembParser::ExportEventVariables(std::string &package_name, QuestEventID case EVENT_TRADE: { if(extra_pointers) { for(size_t i = 0; i < extra_pointers->size(); ++i) { - ItemInst *inst = reinterpret_cast(extra_pointers->at(i)); + ItemInst *inst = EQEmu::any_cast(extra_pointers->at(i)); std::string var_name = "item"; var_name += std::to_string(static_cast(i + 1)); diff --git a/zone/embparser.h b/zone/embparser.h index 3cb3a57f7..1357ae20b 100644 --- a/zone/embparser.h +++ b/zone/embparser.h @@ -45,17 +45,17 @@ public: ~PerlembParser(); virtual int EventNPC(QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); virtual int EventGlobalNPC(QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); virtual int EventPlayer(QuestEventID evt, Client *client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); virtual int EventGlobalPlayer(QuestEventID evt, Client *client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); virtual int EventItem(QuestEventID evt, Client *client, ItemInst *item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); virtual int EventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); virtual bool HasQuestSub(uint32 npcid, QuestEventID evt); virtual bool HasGlobalQuestSub(QuestEventID evt); @@ -86,7 +86,7 @@ private: void ExportVarComplex(const char *pkgprefix, const char *varname, const char *value); int EventCommon(QuestEventID event, uint32 objid, const char * data, NPC* npcmob, ItemInst* iteminst, Mob* mob, - uint32 extradata, bool global, std::vector *extra_pointers); + uint32 extradata, bool global, std::vector *extra_pointers); int SendCommands(const char *pkgprefix, const char *event, uint32 npcid, Mob* other, Mob* mob, ItemInst *iteminst); void MapFunctions(); @@ -103,7 +103,7 @@ private: void ExportZoneVariables(std::string &package_name); void ExportItemVariables(std::string &package_name, Mob *mob); void ExportEventVariables(std::string &package_name, QuestEventID event, uint32 objid, const char * data, - NPC* npcmob, ItemInst* iteminst, Mob* mob, uint32 extradata, std::vector *extra_pointers); + NPC* npcmob, ItemInst* iteminst, Mob* mob, uint32 extradata, std::vector *extra_pointers); std::map npc_quest_status_; PerlQuestStatus global_npc_quest_status_; diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index b2c4b02ad..5adbdedbd 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -28,8 +28,10 @@ #include "embxs.h" #include "entity.h" #include "zone.h" +#include "queryserv.h" extern Zone* zone; +extern QueryServ* QServ; /* @@ -3370,6 +3372,36 @@ XS(XS__clear_npctype_cache) XSRETURN_EMPTY; } +XS(XS__qs_send_query); +XS(XS__qs_send_query) +{ + dXSARGS; + if (items != 1){ + Perl_croak(aTHX_ "Usage: qs_send_query(query)"); + } + else{ + // char *Query = (char *)SvPV_nolen(ST(0)); + std::string Query = (std::string)SvPV_nolen(ST(0)); + QServ->SendQuery(Query); + } + XSRETURN_EMPTY; +} + +XS(XS__qs_player_event); +XS(XS__qs_player_event) +{ + dXSARGS; + if (items != 2){ + Perl_croak(aTHX_ "Usage: qs_player_event(char_id, event_desc)"); + } + else{ + int char_id = (int)SvIV(ST(0)); + std::string event_desc = (std::string)SvPV_nolen(ST(1)); + QServ->PlayerLogEvent(Player_Log_Quest, char_id, event_desc); + } + XSRETURN_EMPTY; +} + /* This is the callback perl will look for to setup the quest package's XSUBs @@ -3591,6 +3623,8 @@ EXTERN_C XS(boot_quest) newXS(strcpy(buf, "enablerecipe"), XS__enablerecipe, file); newXS(strcpy(buf, "disablerecipe"), XS__disablerecipe, file); newXS(strcpy(buf, "clear_npctype_cache"), XS__clear_npctype_cache, file); + newXS(strcpy(buf, "qs_send_query"), XS__qs_send_query, file); + newXS(strcpy(buf, "qs_player_event"), XS__qs_player_event, file); XSRETURN_YES; } diff --git a/zone/entity.cpp b/zone/entity.cpp index 469bf8111..16fbe7077 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -1476,7 +1476,7 @@ void EntityList::QueueClientsStatus(Mob *sender, const EQApplicationPacket *app, void EntityList::DuelMessage(Mob *winner, Mob *loser, bool flee) { if (winner->GetLevelCon(winner->GetLevel(), loser->GetLevel()) > 2) { - std::vector args; + std::vector args; args.push_back(winner); args.push_back(loser); @@ -1598,7 +1598,7 @@ Corpse *EntityList::GetCorpseByName(const char *name) Spawn2 *EntityList::GetSpawnByID(uint32 id) { - if (!zone) + if (!zone || !zone->IsLoaded()) return nullptr; LinkedListIterator iterator(zone->spawn2_list); @@ -2847,7 +2847,7 @@ void EntityList::ClearFeignAggro(Mob *targ) } if (targ->IsClient()) { - std::vector args; + std::vector args; args.push_back(it->second); int i = parse->EventPlayer(EVENT_FEIGN_DEATH, targ->CastToClient(), "", 0, &args); if (i != 0) { @@ -3244,7 +3244,7 @@ void EntityList::ProcessMove(Client *c, float x, float y, float z) if (evt.npc) { parse->EventNPC(evt.event_id, evt.npc, evt.client, "", 0); } else { - std::vector args; + std::vector args; args.push_back(&evt.area_id); args.push_back(&evt.area_type); parse->EventPlayer(evt.event_id, evt.client, "", 0, &args); @@ -3298,7 +3298,7 @@ void EntityList::ProcessMove(NPC *n, float x, float y, float z) for (auto iter = events.begin(); iter != events.end(); ++iter) { quest_proximity_event& evt = (*iter); - std::vector args; + std::vector args; args.push_back(&evt.area_id); args.push_back(&evt.area_type); parse->EventNPC(evt.event_id, evt.npc, evt.client, "", 0, &args); @@ -4170,6 +4170,20 @@ void EntityList::SignalAllClients(uint32 data) } } +uint16 EntityList::GetClientCount(){ + uint16 ClientCount = 0; + std::list client_list; + entity_list.GetClientList(client_list); + std::list::iterator iter = client_list.begin(); + while (iter != client_list.end()) { + Client *entry = (*iter); + entry->GetCleanName(); + ClientCount++; + iter++; + } + return ClientCount; +} + void EntityList::GetMobList(std::list &m_list) { m_list.clear(); diff --git a/zone/entity.h b/zone/entity.h index d61298b0a..cc302fe75 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -398,6 +398,7 @@ public: void UpdateFindableNPCState(NPC *n, bool Remove); void HideCorpses(Client *c, uint8 CurrentMode, uint8 NewMode); + uint16 GetClientCount(); void GetMobList(std::list &m_list); void GetNPCList(std::list &n_list); void GetMercList(std::list &n_list); diff --git a/zone/exp.cpp b/zone/exp.cpp index 73d339af6..9415022ca 100644 --- a/zone/exp.cpp +++ b/zone/exp.cpp @@ -22,6 +22,9 @@ #include "../common/string_util.h" #include "../common/rulesys.h" #include "quest_parser_collection.h" +#include "queryserv.h" + +extern QueryServ* QServ; static uint32 MaxBankedGroupLeadershipPoints(int Level) @@ -212,7 +215,6 @@ void Client::SetEXP(uint32 set_exp, uint32 set_aaxp, bool isrezzexp) { return; // Must be invalid class/race } - if ((set_exp + set_aaxp) > (m_pp.exp+m_pp.expAA)) { if (isrezzexp) this->Message_StringID(MT_Experience, REZ_REGAIN); @@ -288,6 +290,14 @@ void Client::SetEXP(uint32 set_exp, uint32 set_aaxp, bool isrezzexp) { //Message(15, "You have gained %d skill points!!", m_pp.aapoints - last_unspentAA); char val1[20]={0}; Message_StringID(MT_Experience, GAIN_ABILITY_POINT,ConvertArray(m_pp.aapoints, val1),m_pp.aapoints == 1 ? "" : "(s)"); //You have gained an ability point! You now have %1 ability point%2. + + /* QS: PlayerLogAARate */ + if (RuleB(QueryServ, PlayerLogAARate)){ + int add_points = (m_pp.aapoints - last_unspentAA); + std::string query = StringFormat("INSERT INTO `qs_player_aa_rate_hourly` (char_id, aa_count, hour_time) VALUES (%i, %i, UNIX_TIMESTAMP() - MOD(UNIX_TIMESTAMP(), 3600)) ON DUPLICATE KEY UPDATE `aa_count` = `aa_count` + %i", this->CharacterID(), add_points, add_points); + QServ->SendQuery(query.c_str()); + } + //Message(15, "You now have %d skill points available to spend.", m_pp.aapoints); } @@ -299,12 +309,10 @@ void Client::SetEXP(uint32 set_exp, uint32 set_aaxp, bool isrezzexp) { if(check_level > maxlevel) { check_level = maxlevel; - if(RuleB(Character, KeepLevelOverMax)) - { + if(RuleB(Character, KeepLevelOverMax)) { set_exp = GetEXPForLevel(GetLevel()+1); } - else - { + else { set_exp = GetEXPForLevel(maxlevel); } } @@ -314,8 +322,7 @@ void Client::SetEXP(uint32 set_exp, uint32 set_aaxp, bool isrezzexp) { if(MaxLevel){ if(GetLevel() >= MaxLevel){ uint32 expneeded = GetEXPForLevel(MaxLevel); - if(set_exp > expneeded) - { + if(set_exp > expneeded) { set_exp = expneeded; } } @@ -327,11 +334,11 @@ void Client::SetEXP(uint32 set_exp, uint32 set_aaxp, bool isrezzexp) { if (GetLevel() == check_level-1){ Message_StringID(MT_Experience, GAIN_LEVEL,ConvertArray(check_level,val1)); SendLevelAppearance(); - //Message(15, "You have gained a level! Welcome to level %i!", check_level); + /* Message(15, "You have gained a level! Welcome to level %i!", check_level); */ } if (GetLevel() == check_level){ Message_StringID(MT_Experience, LOSE_LEVEL,ConvertArray(check_level,val1)); - //Message(15, "You lost a level! You are now level %i!", check_level); + /* Message(15, "You lost a level! You are now level %i!", check_level); */ } else Message(15, "Welcome to level %i!", check_level); @@ -352,8 +359,7 @@ void Client::SetEXP(uint32 set_exp, uint32 set_aaxp, bool isrezzexp) { //If were at max level then stop gaining experience if we make it to the cap if(GetLevel() == maxlevel - 1){ uint32 expneeded = GetEXPForLevel(maxlevel); - if(set_exp > expneeded) - { + if(set_exp > expneeded) { set_exp = expneeded; } } @@ -405,15 +411,13 @@ void Client::SetLevel(uint8 set_level, bool command) level = set_level; - if(IsRaidGrouped()) - { + if(IsRaidGrouped()) { Raid *r = this->GetRaid(); if(r){ r->UpdateLevel(GetName(), set_level); } } - if(set_level > m_pp.level2) - { + if(set_level > m_pp.level2) { if(m_pp.level2 == 0) m_pp.points += 5; else @@ -423,6 +427,18 @@ void Client::SetLevel(uint8 set_level, bool command) } if(set_level > m_pp.level) { parse->EventPlayer(EVENT_LEVEL_UP, this, "", 0); + /* QS: PlayerLogLevels */ + if (RuleB(QueryServ, PlayerLogLevels)){ + std::string event_desc = StringFormat("Leveled UP :: to Level:%i from Level:%i in zoneid:%i instid:%i", set_level, m_pp.level, this->GetZoneID(), this->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_Levels, this->CharacterID(), event_desc); + } + } + else if (set_level < m_pp.level){ + /* QS: PlayerLogLevels */ + if (RuleB(QueryServ, PlayerLogLevels)){ + std::string event_desc = StringFormat("Leveled DOWN :: to Level:%i from Level:%i in zoneid:%i instid:%i", set_level, m_pp.level, this->GetZoneID(), this->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_Levels, this->CharacterID(), event_desc); + } } m_pp.level = set_level; @@ -432,34 +448,34 @@ void Client::SetLevel(uint8 set_level, bool command) lu->exp = 0; } else { - float tmpxp = (float) ( (float) m_pp.exp - GetEXPForLevel( GetLevel() )) / - ( (float) GetEXPForLevel(GetLevel()+1) - GetEXPForLevel(GetLevel())); + float tmpxp = (float) ( (float) m_pp.exp - GetEXPForLevel( GetLevel() )) / ( (float) GetEXPForLevel(GetLevel()+1) - GetEXPForLevel(GetLevel())); lu->exp = (uint32)(330.0f * tmpxp); } QueuePacket(outapp); safe_delete(outapp); this->SendAppearancePacket(AT_WhoLevel, set_level); // who level change - LogFile->write(EQEMuLog::Normal,"Setting Level for %s to %i", GetName(), set_level); + LogFile->write(EQEMuLog::Normal,"Setting Level for %s to %i", GetName(), set_level); CalcBonuses(); - if(!RuleB(Character, HealOnLevel)) - { + + if(!RuleB(Character, HealOnLevel)) { int mhp = CalcMaxHP(); if(GetHP() > mhp) SetHP(mhp); } - else - { + else { SetHP(CalcMaxHP()); // Why not, lets give them a free heal } - DoTributeUpdate(); + DoTributeUpdate(); SendHPUpdate(); SetMana(CalcMaxMana()); UpdateWho(); - if(GetMerc()) - UpdateMercLevel(); + + if(GetMerc()) + UpdateMercLevel(); + Save(); } @@ -508,23 +524,13 @@ uint32 Client::GetEXPForLevel(uint16 check_level) uint32 finalxp = uint32(base * mod); finalxp = mod_client_xp_for_level(finalxp, check_level); - return(finalxp); + return finalxp; } -void Client::AddLevelBasedExp(uint8 exp_percentage, uint8 max_level) { - - if (exp_percentage > 100) - { - exp_percentage = 100; - } - - if (!max_level || GetLevel() < max_level) - { - max_level = GetLevel(); - } - - uint32 newexp = GetEXP() + ((GetEXPForLevel(max_level + 1) - GetEXPForLevel(max_level)) * exp_percentage / 100); - +void Client::AddLevelBasedExp(uint8 exp_percentage, uint8 max_level) { + if (exp_percentage > 100) { exp_percentage = 100; } + if (!max_level || GetLevel() < max_level) { max_level = GetLevel(); } + uint32 newexp = GetEXP() + ((GetEXPForLevel(max_level + 1) - GetEXPForLevel(max_level)) * exp_percentage / 100); SetEXP(newexp, GetAAXP()); } @@ -666,28 +672,25 @@ void Client::SendLeadershipEXPUpdate() { } uint32 Client::GetCharMaxLevelFromQGlobal() { + QGlobalCache *char_c = nullptr; + char_c = this->GetQGlobals(); - QGlobalCache *char_c = nullptr; - char_c = this->GetQGlobals(); + std::list globalMap; + uint32 ntype = 0; - std::list globalMap; - uint32 ntype = 0; + if(char_c) { + QGlobalCache::Combine(globalMap, char_c->GetBucket(), ntype, this->CharacterID(), zone->GetZoneID()); + } - if(char_c) - { - QGlobalCache::Combine(globalMap, char_c->GetBucket(), ntype, this->CharacterID(), zone->GetZoneID()); - } + std::list::iterator iter = globalMap.begin(); + uint32 gcount = 0; + while(iter != globalMap.end()) { + if((*iter).name.compare("CharMaxLevel") == 0){ + return atoi((*iter).value.c_str()); + } + ++iter; + ++gcount; + } - std::list::iterator iter = globalMap.begin(); - uint32 gcount = 0; - while(iter != globalMap.end()) - { - if((*iter).name.compare("CharMaxLevel") == 0){ - return atoi((*iter).value.c_str()); - } - ++iter; - ++gcount; - } - - return false; // Default is false + return false; } diff --git a/zone/forage.cpp b/zone/forage.cpp index aaa3451d5..e85cbda4a 100644 --- a/zone/forage.cpp +++ b/zone/forage.cpp @@ -357,7 +357,7 @@ void Client::GoFish() } } - std::vector args; + std::vector args; args.push_back(inst); parse->EventPlayer(EVENT_FISH_SUCCESS, this, "", inst != nullptr ? inst->GetItem()->ID : 0, &args); } @@ -471,7 +471,7 @@ void Client::ForageItem(bool guarantee) { } } - std::vector args; + std::vector args; args.push_back(inst); parse->EventPlayer(EVENT_FORAGE_SUCCESS, this, "", inst ? inst->GetItem()->ID : 0, &args); diff --git a/zone/groups.cpp b/zone/groups.cpp index 598fa4989..5c34cf359 100644 --- a/zone/groups.cpp +++ b/zone/groups.cpp @@ -193,11 +193,10 @@ void Group::SplitMoney(uint32 copper, uint32 silver, uint32 gold, uint32 platinu for (i = 0; i < MAX_GROUP_MEMBERS; i++) { if (members[i] != nullptr && members[i]->IsClient()) { // If Group Member is Client - Client *c = members[i]->CastToClient(); - //I could not get MoneyOnCorpse to work, so we use this - c->AddMoneyToPP(cpsplit, spsplit, gpsplit, ppsplit, true); - - c->Message(2, msg.c_str()); + Client *c = members[i]->CastToClient(); + //I could not get MoneyOnCorpse to work, so we use this + c->AddMoneyToPP(cpsplit, spsplit, gpsplit, ppsplit, true); + c->Message(2, msg.c_str()); } } } diff --git a/zone/guild_mgr.cpp b/zone/guild_mgr.cpp index 087878ffc..37cc7a3de 100644 --- a/zone/guild_mgr.cpp +++ b/zone/guild_mgr.cpp @@ -644,104 +644,66 @@ GuildBankManager::~GuildBankManager() } } -bool GuildBankManager::Load(uint32 GuildID) +bool GuildBankManager::Load(uint32 guildID) { - const char *LoadQuery = "SELECT `area`, `slot`, `itemid`, `qty`, `donator`, `permissions`, `whofor` from `guild_bank` " - "WHERE `guildid` = %i"; - - char errbuf[MYSQL_ERRMSG_SIZE]; - - char* query = 0; - - MYSQL_RES *result; - - MYSQL_ROW row; - - if(database.RunQuery(query, MakeAnyLenString(&query, LoadQuery, GuildID), errbuf, &result)) - { - GuildBank *Bank = new GuildBank; - - Bank->GuildID = GuildID; - - for(int i = 0; i < GUILD_BANK_MAIN_AREA_SIZE; ++i) - Bank->Items.MainArea[i].ItemID = 0; - - for(int i = 0; i < GUILD_BANK_DEPOSIT_AREA_SIZE; ++i) - Bank->Items.DepositArea[i].ItemID = 0; - - char Donator[64], WhoFor[64]; - - while((row = mysql_fetch_row(result))) - { - int Area = atoi(row[0]); - - int Slot = atoi(row[1]); - - int ItemID = atoi(row[2]); - - int Qty = atoi(row[3]); - - if(row[4]) - strn0cpy(Donator, row[4], sizeof(Donator)); - else - Donator[0] = '\0'; - - int Permissions = atoi(row[5]); - - if(row[6]) - strn0cpy(WhoFor, row[6], sizeof(WhoFor)); - else - WhoFor[0] = '\0'; - - if(Area == GuildBankMainArea) - { - if((Slot >= 0) && (Slot < GUILD_BANK_MAIN_AREA_SIZE)) - { - Bank->Items.MainArea[Slot].ItemID = ItemID; - - Bank->Items.MainArea[Slot].Quantity = Qty; - - strn0cpy(Bank->Items.MainArea[Slot].Donator, Donator, sizeof(Donator)); - - Bank->Items.MainArea[Slot].Permissions = Permissions; - - strn0cpy(Bank->Items.MainArea[Slot].WhoFor, WhoFor, sizeof(WhoFor)); - } - } - else - { - if((Slot >= 0 ) && (Slot < GUILD_BANK_DEPOSIT_AREA_SIZE)) - { - Bank->Items.DepositArea[Slot].ItemID = ItemID; - - Bank->Items.DepositArea[Slot].Quantity = Qty; - - strn0cpy(Bank->Items.DepositArea[Slot].Donator, Donator, sizeof(Donator)); - - Bank->Items.DepositArea[Slot].Permissions = Permissions; - - strn0cpy(Bank->Items.DepositArea[Slot].WhoFor, WhoFor, sizeof(WhoFor)); - } - } - - } - mysql_free_result(result); - - safe_delete_array(query); - - Banks.push_back(Bank); - } - else - { - _log(GUILDS__BANK_ERROR, "Error Loading guild bank: %s, %s", query, errbuf); - - safe_delete_array(query); + std::string query = StringFormat("SELECT `area`, `slot`, `itemid`, `qty`, `donator`, `permissions`, `whofor` " + "FROM `guild_bank` WHERE `guildid` = %i", guildID); + auto results = database.QueryDatabase(query); + if(!results.Success()) { + _log(GUILDS__BANK_ERROR, "Error Loading guild bank: %s, %s", query.c_str(), results.ErrorMessage().c_str()); return false; } - return true; + GuildBank *bank = new GuildBank; + bank->GuildID = guildID; + + for(int i = 0; i < GUILD_BANK_MAIN_AREA_SIZE; ++i) + bank->Items.MainArea[i].ItemID = 0; + + for(int i = 0; i < GUILD_BANK_DEPOSIT_AREA_SIZE; ++i) + bank->Items.DepositArea[i].ItemID = 0; + + char donator[64], whoFor[64]; + + for (auto row = results.begin(); row != results.end(); ++row) + { + int area = atoi(row[0]); + int slot = atoi(row[1]); + int itemID = atoi(row[2]); + int qty = atoi(row[3]); + + if(row[4]) + strn0cpy(donator, row[4], sizeof(donator)); + else + donator[0] = '\0'; + + int permissions = atoi(row[5]); + + if(row[6]) + strn0cpy(whoFor, row[6], sizeof(whoFor)); + else + whoFor[0] = '\0'; + + if(slot < 0 || + ((area != GuildBankMainArea || slot >= GUILD_BANK_MAIN_AREA_SIZE) || + (area == GuildBankMainArea || slot >= GUILD_BANK_DEPOSIT_AREA_SIZE))) + continue; + + bank->Items.MainArea[slot].ItemID = itemID; + bank->Items.MainArea[slot].Quantity = qty; + + strn0cpy(bank->Items.MainArea[slot].Donator, donator, sizeof(donator)); + + bank->Items.MainArea[slot].Permissions = permissions; + + strn0cpy(bank->Items.MainArea[slot].WhoFor, whoFor, sizeof(whoFor)); + } + + Banks.push_back(bank); + + return true; } bool GuildBankManager::IsLoaded(uint32 GuildID) @@ -973,156 +935,127 @@ bool GuildBankManager::AddItem(uint32 GuildID, uint8 Area, uint32 ItemID, int32 return true; } -int GuildBankManager::Promote(uint32 GuildID, int SlotID) +int GuildBankManager::Promote(uint32 guildID, int slotID) { - if((SlotID < 0) || (SlotID > (GUILD_BANK_DEPOSIT_AREA_SIZE - 1))) + if((slotID < 0) || (slotID > (GUILD_BANK_DEPOSIT_AREA_SIZE - 1))) return -1; - std::list::iterator Iterator = GetGuildBank(GuildID); + auto iter = GetGuildBank(guildID); - if(Iterator == Banks.end()) - { + if(iter == Banks.end()) return -1; - } - if((*Iterator)->Items.DepositArea[SlotID].ItemID == 0) - { + if((*iter)->Items.DepositArea[slotID].ItemID == 0) return -1; - } - int MainSlot = -1; + int mainSlot = -1; for(int i = 0; i < GUILD_BANK_MAIN_AREA_SIZE; ++i) - if((*Iterator)->Items.MainArea[i].ItemID == 0) - { - MainSlot = i; - + if((*iter)->Items.MainArea[i].ItemID == 0) { + mainSlot = i; break; } - if(MainSlot == -1) + if(mainSlot == -1) return -1; + (*iter)->Items.MainArea[mainSlot].ItemID = (*iter)->Items.DepositArea[slotID].ItemID; + (*iter)->Items.MainArea[mainSlot].Quantity = (*iter)->Items.DepositArea[slotID].Quantity; + (*iter)->Items.MainArea[mainSlot].Permissions = (*iter)->Items.DepositArea[slotID].Permissions; - (*Iterator)->Items.MainArea[MainSlot].ItemID = (*Iterator)->Items.DepositArea[SlotID].ItemID; - - (*Iterator)->Items.MainArea[MainSlot].Quantity = (*Iterator)->Items.DepositArea[SlotID].Quantity; - - strn0cpy((*Iterator)->Items.MainArea[MainSlot].Donator, (*Iterator)->Items.DepositArea[SlotID].Donator, sizeof((*Iterator)->Items.MainArea[MainSlot].Donator)); - (*Iterator)->Items.MainArea[MainSlot].Permissions = (*Iterator)->Items.DepositArea[SlotID].Permissions; - - strn0cpy((*Iterator)->Items.MainArea[MainSlot].WhoFor, (*Iterator)->Items.DepositArea[SlotID].WhoFor, sizeof((*Iterator)->Items.MainArea[MainSlot].WhoFor)); - - const char *Query="UPDATE `guild_bank` SET `area` = 1, `slot` = %i WHERE `guildid` = %i AND `area` = 0 AND `slot` = %i LIMIT 1"; - - char errbuf[MYSQL_ERRMSG_SIZE]; - - char* query = 0; - - if(!database.RunQuery(query, MakeAnyLenString(&query, Query, MainSlot, GuildID, SlotID), errbuf)) - { - _log(GUILDS__BANK_ERROR, "error promoting item: %s : %s", query, errbuf); - - safe_delete_array(query); + strn0cpy((*iter)->Items.MainArea[mainSlot].Donator, (*iter)->Items.DepositArea[slotID].Donator, sizeof((*iter)->Items.MainArea[mainSlot].Donator)); + strn0cpy((*iter)->Items.MainArea[mainSlot].WhoFor, (*iter)->Items.DepositArea[slotID].WhoFor, sizeof((*iter)->Items.MainArea[mainSlot].WhoFor)); + std::string query = StringFormat("UPDATE `guild_bank` SET `area` = 1, `slot` = %i " + "WHERE `guildid` = %i AND `area` = 0 AND `slot` = %i " + "LIMIT 1", mainSlot, guildID, slotID); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + _log(GUILDS__BANK_ERROR, "error promoting item: %s : %s", query.c_str(), results.ErrorMessage().c_str()); return -1; } - safe_delete_array(query); + (*iter)->Items.DepositArea[slotID].ItemID = 0; - (*Iterator)->Items.DepositArea[SlotID].ItemID = 0; - - const Item_Struct *Item = database.GetItem((*Iterator)->Items.MainArea[MainSlot].ItemID); + const Item_Struct *Item = database.GetItem((*iter)->Items.MainArea[mainSlot].ItemID); GuildBankItemUpdate_Struct gbius; if(!Item->Stackable) - gbius.Init(GuildBankItemUpdate, 1, MainSlot, GuildBankMainArea, 1, Item->ID, Item->Icon, 1, 0, 0, 0); + gbius.Init(GuildBankItemUpdate, 1, mainSlot, GuildBankMainArea, 1, Item->ID, Item->Icon, 1, 0, 0, 0); else { - if((*Iterator)->Items.MainArea[MainSlot].Quantity == Item->StackSize) - gbius.Init(GuildBankItemUpdate, 1, MainSlot, GuildBankMainArea, 1, Item->ID, Item->Icon, - (*Iterator)->Items.MainArea[MainSlot].Quantity, 0, 0, 0); + if((*iter)->Items.MainArea[mainSlot].Quantity == Item->StackSize) + gbius.Init(GuildBankItemUpdate, 1, mainSlot, GuildBankMainArea, 1, Item->ID, Item->Icon, + (*iter)->Items.MainArea[mainSlot].Quantity, 0, 0, 0); else - gbius.Init(GuildBankItemUpdate, 1, MainSlot, GuildBankMainArea, 1, Item->ID, Item->Icon, - (*Iterator)->Items.MainArea[MainSlot].Quantity, 0, 1, 0); + gbius.Init(GuildBankItemUpdate, 1, mainSlot, GuildBankMainArea, 1, Item->ID, Item->Icon, + (*iter)->Items.MainArea[mainSlot].Quantity, 0, 1, 0); } strn0cpy(gbius.ItemName, Item->Name, sizeof(gbius.ItemName)); - entity_list.QueueClientsGuildBankItemUpdate(&gbius, GuildID); + entity_list.QueueClientsGuildBankItemUpdate(&gbius, guildID); - gbius.Init(GuildBankItemUpdate, 1, SlotID, GuildBankDepositArea, 0, 0, 0, 0, 0, 0, 0); + gbius.Init(GuildBankItemUpdate, 1, slotID, GuildBankDepositArea, 0, 0, 0, 0, 0, 0, 0); - entity_list.QueueClientsGuildBankItemUpdate(&gbius, GuildID); + entity_list.QueueClientsGuildBankItemUpdate(&gbius, guildID); - return MainSlot; + return mainSlot; } -void GuildBankManager::SetPermissions(uint32 GuildID, uint16 SlotID, uint32 Permissions, const char *MemberName) +void GuildBankManager::SetPermissions(uint32 guildID, uint16 slotID, uint32 permissions, const char *memberName) { - if((SlotID > (GUILD_BANK_MAIN_AREA_SIZE - 1))) + if((slotID > (GUILD_BANK_MAIN_AREA_SIZE - 1))) return; - std::list::iterator Iterator = GetGuildBank(GuildID); + auto iter = GetGuildBank(guildID); - if(Iterator == Banks.end()) + if(iter == Banks.end()) + return; + + if((*iter)->Items.MainArea[slotID].ItemID == 0) + return; + + std::string query = StringFormat("UPDATE `guild_bank` SET `permissions` = %i, `whofor` = '%s' " + "WHERE `guildid` = %i AND `area` = 1 AND `slot` = %i LIMIT 1", + permissions, memberName, guildID, slotID); + auto results = database.QueryDatabase(query); + if(!results.Success()) { + _log(GUILDS__BANK_ERROR, "error changing permissions: %s : %s", query.c_str(), results.ErrorMessage().c_str()); return; } - if((*Iterator)->Items.MainArea[SlotID].ItemID == 0) - { - return; - } + (*iter)->Items.MainArea[slotID].Permissions = permissions; - const char *Query="UPDATE `guild_bank` SET `permissions` = %i, `whofor` = '%s' WHERE `guildid` = %i AND `area` = 1 AND `slot` = %i LIMIT 1"; - - char errbuf[MYSQL_ERRMSG_SIZE]; - - char* query = 0; - - if(!database.RunQuery(query, MakeAnyLenString(&query, Query, Permissions, MemberName, GuildID, SlotID), errbuf)) - { - _log(GUILDS__BANK_ERROR, "error changing permissions: %s : %s", query, errbuf); - - safe_delete_array(query); - - return; - } - - safe_delete_array(query); - - (*Iterator)->Items.MainArea[SlotID].Permissions = Permissions; - - if(Permissions == GuildBankSingleMember) - strn0cpy((*Iterator)->Items.MainArea[SlotID].WhoFor, MemberName, sizeof((*Iterator)->Items.MainArea[SlotID].WhoFor)); + if(permissions == GuildBankSingleMember) + strn0cpy((*iter)->Items.MainArea[slotID].WhoFor, memberName, sizeof((*iter)->Items.MainArea[slotID].WhoFor)); else - (*Iterator)->Items.MainArea[SlotID].WhoFor[0] = '\0'; + (*iter)->Items.MainArea[slotID].WhoFor[0] = '\0'; - - const Item_Struct *Item = database.GetItem((*Iterator)->Items.MainArea[SlotID].ItemID); + const Item_Struct *Item = database.GetItem((*iter)->Items.MainArea[slotID].ItemID); GuildBankItemUpdate_Struct gbius; if(!Item->Stackable) - gbius.Init(GuildBankItemUpdate, 1, SlotID, GuildBankMainArea, 1, Item->ID, Item->Icon, 1, (*Iterator)->Items.MainArea[SlotID].Permissions, 0, 0); + gbius.Init(GuildBankItemUpdate, 1, slotID, GuildBankMainArea, 1, Item->ID, Item->Icon, 1, (*iter)->Items.MainArea[slotID].Permissions, 0, 0); else { - if((*Iterator)->Items.MainArea[SlotID].Quantity == Item->StackSize) - gbius.Init(GuildBankItemUpdate, 1, SlotID, GuildBankMainArea, 1, Item->ID, Item->Icon, - (*Iterator)->Items.MainArea[SlotID].Quantity, (*Iterator)->Items.MainArea[SlotID].Permissions, 0, 0); + if((*iter)->Items.MainArea[slotID].Quantity == Item->StackSize) + gbius.Init(GuildBankItemUpdate, 1, slotID, GuildBankMainArea, 1, Item->ID, Item->Icon, + (*iter)->Items.MainArea[slotID].Quantity, (*iter)->Items.MainArea[slotID].Permissions, 0, 0); else - gbius.Init(GuildBankItemUpdate, 1, SlotID, GuildBankMainArea, 1, Item->ID, Item->Icon, - (*Iterator)->Items.MainArea[SlotID].Quantity, (*Iterator)->Items.MainArea[SlotID].Permissions, 1, 0); + gbius.Init(GuildBankItemUpdate, 1, slotID, GuildBankMainArea, 1, Item->ID, Item->Icon, + (*iter)->Items.MainArea[slotID].Quantity, (*iter)->Items.MainArea[slotID].Permissions, 1, 0); } strn0cpy(gbius.ItemName, Item->Name, sizeof(gbius.ItemName)); - strn0cpy(gbius.WhoFor, (*Iterator)->Items.MainArea[SlotID].WhoFor, sizeof(gbius.WhoFor)); + strn0cpy(gbius.WhoFor, (*iter)->Items.MainArea[slotID].WhoFor, sizeof(gbius.WhoFor)); - entity_list.QueueClientsGuildBankItemUpdate(&gbius, GuildID); + entity_list.QueueClientsGuildBankItemUpdate(&gbius, guildID); } ItemInst* GuildBankManager::GetItem(uint32 GuildID, uint16 Area, uint16 SlotID, uint32 Quantity) @@ -1208,90 +1141,73 @@ std::list::iterator GuildBankManager::GetGuildBank(uint32 GuildID) return Iterator; } -bool GuildBankManager::DeleteItem(uint32 GuildID, uint16 Area, uint16 SlotID, uint32 Quantity) +bool GuildBankManager::DeleteItem(uint32 guildID, uint16 area, uint16 slotID, uint32 quantity) { - std::list::iterator Iterator = GetGuildBank(GuildID); + auto iter = GetGuildBank(guildID); - if(Iterator == Banks.end()) + if(iter == Banks.end()) return false; - char errbuf[MYSQL_ERRMSG_SIZE]; - - char* query = 0; - GuildBankItem* BankArea = nullptr; - if(Area == GuildBankMainArea) + if(area == GuildBankMainArea) { - if(SlotID > (GUILD_BANK_MAIN_AREA_SIZE - 1)) + if(slotID > (GUILD_BANK_MAIN_AREA_SIZE - 1)) return false; - BankArea = &(*Iterator)->Items.MainArea[0]; - } - else - { - if(SlotID > (GUILD_BANK_DEPOSIT_AREA_SIZE - 1)) + BankArea = &(*iter)->Items.MainArea[0]; + } else { + if(slotID > (GUILD_BANK_DEPOSIT_AREA_SIZE - 1)) return false; - BankArea = &(*Iterator)->Items.DepositArea[0]; + BankArea = &(*iter)->Items.DepositArea[0]; } + bool deleted = true; - bool Deleted = true; - - const Item_Struct *Item = database.GetItem(BankArea[SlotID].ItemID); - - if(!Item->Stackable || (Quantity >= BankArea[SlotID].Quantity)) - { - const char *Query = "DELETE from `guild_bank` where `guildid` = %i AND `area` = %i AND `slot` = %i LIMIT 1"; - - if(!database.RunQuery(query, MakeAnyLenString(&query, Query, GuildID, Area, SlotID), errbuf)) - { - _log(GUILDS__BANK_ERROR, "Delete item failed. %s : %s", query, errbuf); - - safe_delete_array(query); + const Item_Struct *Item = database.GetItem(BankArea[slotID].ItemID); + if(!Item->Stackable || (quantity >= BankArea[slotID].Quantity)) { + std::string query = StringFormat("DELETE FROM `guild_bank` WHERE `guildid` = %i " + "AND `area` = %i AND `slot` = %i LIMIT 1", + guildID, area, slotID); + auto results = database.QueryDatabase(query); + if(!results.Success()) { + _log(GUILDS__BANK_ERROR, "Delete item failed. %s : %s", query.c_str(), results.ErrorMessage().c_str()); return false; } - safe_delete_array(query); - - BankArea[SlotID].ItemID = 0; - } - else - { - const char *Query = "UPDATE `guild_bank` SET `qty` = %i where `guildid` = %i AND `area` = %i AND `slot` = %i LIMIT 1"; - - if(!database.RunQuery(query, MakeAnyLenString(&query, Query, BankArea[SlotID].Quantity - Quantity, - GuildID, Area, SlotID), errbuf)) - { - _log(GUILDS__BANK_ERROR, "Update item failed. %s : %s", query, errbuf); - - safe_delete_array(query); + BankArea[slotID].ItemID = 0; + } else { + std::string query = StringFormat("UPDATE `guild_bank` SET `qty` = %i WHERE `guildid` = %i " + "AND `area` = %i AND `slot` = %i LIMIT 1", + BankArea[slotID].Quantity - quantity, guildID, area, slotID); + auto results = database.QueryDatabase(query); + if(!results.Success()) { + _log(GUILDS__BANK_ERROR, "Update item failed. %s : %s", query.c_str(), results.ErrorMessage().c_str()); return false; } - safe_delete_array(query); + BankArea[slotID].Quantity -= quantity; - BankArea[SlotID].Quantity -= Quantity; - - Deleted = false; + deleted = false; } + GuildBankItemUpdate_Struct gbius; - if(!Deleted) + if(!deleted) { - gbius.Init(GuildBankItemUpdate, 1, SlotID, Area, 1, Item->ID, Item->Icon, BankArea[SlotID].Quantity, BankArea[SlotID].Permissions, 1, 0); + gbius.Init(GuildBankItemUpdate, 1, slotID, area, 1, Item->ID, Item->Icon, BankArea[slotID].Quantity, BankArea[slotID].Permissions, 1, 0); strn0cpy(gbius.ItemName, Item->Name, sizeof(gbius.ItemName)); - strn0cpy(gbius.WhoFor, BankArea[SlotID].WhoFor, sizeof(gbius.WhoFor)); + strn0cpy(gbius.WhoFor, BankArea[slotID].WhoFor, sizeof(gbius.WhoFor)); } else - gbius.Init(GuildBankItemUpdate, 1, SlotID, Area, 0, 0, 0, 0, 0, 0, 0); + gbius.Init(GuildBankItemUpdate, 1, slotID, area, 0, 0, 0, 0, 0, 0, 0); - entity_list.QueueClientsGuildBankItemUpdate(&gbius, GuildID); + entity_list.QueueClientsGuildBankItemUpdate(&gbius, guildID); return true; @@ -1422,26 +1338,20 @@ bool GuildBankManager::SplitStack(uint32 GuildID, uint16 SlotID, uint32 Quantity return true; } -void GuildBankManager::UpdateItemQuantity(uint32 GuildID, uint16 Area, uint16 SlotID, uint32 Quantity) +void GuildBankManager::UpdateItemQuantity(uint32 guildID, uint16 area, uint16 slotID, uint32 quantity) { // Helper method for MergeStacks. Assuming all passed parameters are valid. // - char errbuf[MYSQL_ERRMSG_SIZE]; - - char* query = 0; - - const char *Query = "UPDATE `guild_bank` SET `qty` = %i where `guildid` = %i AND `area` = %i AND `slot` = %i LIMIT 1"; - - if(!database.RunQuery(query, MakeAnyLenString(&query, Query, Quantity, GuildID, Area, SlotID), errbuf)) - { - _log(GUILDS__BANK_ERROR, "Update item quantity failed. %s : %s", query, errbuf); - - safe_delete_array(query); - + std::string query = StringFormat("UPDATE `guild_bank` SET `qty` = %i " + "WHERE `guildid` = %i AND `area` = %i " + "AND `slot` = %i LIMIT 1", + quantity, guildID, area, slotID); + auto results = database.QueryDatabase(query); + if(!results.Success()) { + _log(GUILDS__BANK_ERROR, "Update item quantity failed. %s : %s", query.c_str(), results.ErrorMessage().c_str()); return; } - safe_delete_array(query); } bool GuildBankManager::AllowedToWithdraw(uint32 GuildID, uint16 Area, uint16 SlotID, const char *Name) diff --git a/zone/hate_list.cpp b/zone/hate_list.cpp index c7f44e6ba..86b20d313 100644 --- a/zone/hate_list.cpp +++ b/zone/hate_list.cpp @@ -311,7 +311,7 @@ Mob *HateList::GetTop(Mob *center) } } - if (cur->ent->Sanctuary()) { + if (cur->ent->Sanctuary()) { if(hate == -1) { top = cur->ent; diff --git a/zone/inventory.cpp b/zone/inventory.cpp index 5c5f80122..197841c76 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -822,25 +822,24 @@ bool Client::PushItemOnCursor(const ItemInst& inst, bool client_update) return database.SaveCursor(CharacterID(), s, e); } -bool Client::PutItemInInventory(int16 slot_id, const ItemInst& inst, bool client_update) -{ +bool Client::PutItemInInventory(int16 slot_id, const ItemInst& inst, bool client_update) { mlog(INVENTORY__SLOTS, "Putting item %s (%d) into slot %d", inst.GetItem()->Name, inst.GetItem()->ID, slot_id); + if (slot_id == MainCursor) - { - return PushItemOnCursor(inst,client_update); - } + return PushItemOnCursor(inst, client_update); else m_inv.PutItem(slot_id, inst); - if (client_update) { - SendItemPacket(slot_id, &inst, (slot_id == MainCursor) ? ItemPacketSummonItem : ItemPacketTrade); - } + if (client_update) + SendItemPacket(slot_id, &inst, ((slot_id == MainCursor) ? ItemPacketSummonItem : ItemPacketTrade)); if (slot_id == MainCursor) { - std::list::const_iterator s=m_inv.cursor_begin(),e=m_inv.cursor_end(); + std::list::const_iterator s = m_inv.cursor_begin(), e = m_inv.cursor_end(); return database.SaveCursor(this->CharacterID(), s, e); - } else + } + else { return database.SaveInventory(this->CharacterID(), &inst, slot_id); + } CalcBonuses(); } @@ -1539,7 +1538,7 @@ bool Client::SwapItem(MoveItem_Struct* move_in) { // Also sends trade information to other client of trade session if(RuleB(QueryServ, PlayerLogMoves)) { QSSwapItemAuditor(move_in); } // QS Audit - trade->AddEntity(src_slot_id, dst_slot_id, move_in->number_in_stack); + trade->AddEntity(dst_slot_id, move_in->number_in_stack); return true; } else { diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index a1af1b499..32affc06e 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -1134,7 +1134,7 @@ void Lua_Client::Signal(uint32 id) { void Lua_Client::AddAlternateCurrencyValue(uint32 currency, int amount) { Lua_Safe_Call_Void(); - self->AddAlternateCurrencyValue(currency, amount); + self->AddAlternateCurrencyValue(currency, amount, 1); } void Lua_Client::SendWebLink(const char *site) { diff --git a/zone/lua_parser.cpp b/zone/lua_parser.cpp index 9f082993c..1de119afa 100644 --- a/zone/lua_parser.cpp +++ b/zone/lua_parser.cpp @@ -220,7 +220,7 @@ LuaParser::~LuaParser() { } int LuaParser::EventNPC(QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { evt = ConvertLuaEvent(evt); if(evt >= _LargestEventID) { return 0; @@ -239,7 +239,7 @@ int LuaParser::EventNPC(QuestEventID evt, NPC* npc, Mob *init, std::string data, } int LuaParser::EventGlobalNPC(QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { evt = ConvertLuaEvent(evt); if(evt >= _LargestEventID) { return 0; @@ -257,7 +257,7 @@ int LuaParser::EventGlobalNPC(QuestEventID evt, NPC* npc, Mob *init, std::string } int LuaParser::_EventNPC(std::string package_name, QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers, luabind::adl::object *l_func) { + std::vector *extra_pointers, luabind::adl::object *l_func) { const char *sub_name = LuaEvents[evt]; int start = lua_gettop(L); @@ -316,7 +316,7 @@ int LuaParser::_EventNPC(std::string package_name, QuestEventID evt, NPC* npc, M } int LuaParser::EventPlayer(QuestEventID evt, Client *client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { evt = ConvertLuaEvent(evt); if(evt >= _LargestEventID) { return 0; @@ -334,7 +334,7 @@ int LuaParser::EventPlayer(QuestEventID evt, Client *client, std::string data, u } int LuaParser::EventGlobalPlayer(QuestEventID evt, Client *client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { evt = ConvertLuaEvent(evt); if(evt >= _LargestEventID) { return 0; @@ -352,7 +352,7 @@ int LuaParser::EventGlobalPlayer(QuestEventID evt, Client *client, std::string d } int LuaParser::_EventPlayer(std::string package_name, QuestEventID evt, Client *client, std::string data, uint32 extra_data, - std::vector *extra_pointers, luabind::adl::object *l_func) { + std::vector *extra_pointers, luabind::adl::object *l_func) { const char *sub_name = LuaEvents[evt]; int start = lua_gettop(L); @@ -409,7 +409,7 @@ int LuaParser::_EventPlayer(std::string package_name, QuestEventID evt, Client * } int LuaParser::EventItem(QuestEventID evt, Client *client, ItemInst *item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { evt = ConvertLuaEvent(evt); if(evt >= _LargestEventID) { return 0; @@ -429,7 +429,7 @@ int LuaParser::EventItem(QuestEventID evt, Client *client, ItemInst *item, Mob * } int LuaParser::_EventItem(std::string package_name, QuestEventID evt, Client *client, ItemInst *item, Mob *mob, - std::string data, uint32 extra_data, std::vector *extra_pointers, luabind::adl::object *l_func) { + std::string data, uint32 extra_data, std::vector *extra_pointers, luabind::adl::object *l_func) { const char *sub_name = LuaEvents[evt]; int start = lua_gettop(L); @@ -492,7 +492,7 @@ int LuaParser::_EventItem(std::string package_name, QuestEventID evt, Client *cl } int LuaParser::EventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { evt = ConvertLuaEvent(evt); if(evt >= _LargestEventID) { return 0; @@ -508,7 +508,7 @@ int LuaParser::EventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spe } int LuaParser::_EventSpell(std::string package_name, QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers, luabind::adl::object *l_func) { + std::vector *extra_pointers, luabind::adl::object *l_func) { const char *sub_name = LuaEvents[evt]; int start = lua_gettop(L); @@ -572,7 +572,7 @@ int LuaParser::_EventSpell(std::string package_name, QuestEventID evt, NPC* npc, return 0; } -int LuaParser::EventEncounter(QuestEventID evt, std::string encounter_name, uint32 extra_data, std::vector *extra_pointers) { +int LuaParser::EventEncounter(QuestEventID evt, std::string encounter_name, uint32 extra_data, std::vector *extra_pointers) { evt = ConvertLuaEvent(evt); if(evt >= _LargestEventID) { return 0; @@ -588,7 +588,7 @@ int LuaParser::EventEncounter(QuestEventID evt, std::string encounter_name, uint } int LuaParser::_EventEncounter(std::string package_name, QuestEventID evt, std::string encounter_name, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { const char *sub_name = LuaEvents[evt]; int start = lua_gettop(L); @@ -972,7 +972,7 @@ void LuaParser::MapFunctions(lua_State *L) { } int LuaParser::DispatchEventNPC(QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { evt = ConvertLuaEvent(evt); if(evt >= _LargestEventID) { return 0; @@ -1018,7 +1018,7 @@ int LuaParser::DispatchEventNPC(QuestEventID evt, NPC* npc, Mob *init, std::stri } int LuaParser::DispatchEventPlayer(QuestEventID evt, Client *client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { evt = ConvertLuaEvent(evt); if(evt >= _LargestEventID) { return 0; @@ -1047,7 +1047,7 @@ int LuaParser::DispatchEventPlayer(QuestEventID evt, Client *client, std::string } int LuaParser::DispatchEventItem(QuestEventID evt, Client *client, ItemInst *item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { evt = ConvertLuaEvent(evt); if(evt >= _LargestEventID) { return 0; @@ -1093,7 +1093,7 @@ int LuaParser::DispatchEventItem(QuestEventID evt, Client *client, ItemInst *ite } int LuaParser::DispatchEventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { evt = ConvertLuaEvent(evt); if(evt >= _LargestEventID) { return 0; diff --git a/zone/lua_parser.h b/zone/lua_parser.h index 732f7d235..13cebe0fc 100644 --- a/zone/lua_parser.h +++ b/zone/lua_parser.h @@ -28,19 +28,19 @@ public: ~LuaParser(); virtual int EventNPC(QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); virtual int EventGlobalNPC(QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); virtual int EventPlayer(QuestEventID evt, Client *client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); virtual int EventGlobalPlayer(QuestEventID evt, Client *client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); virtual int EventItem(QuestEventID evt, Client *client, ItemInst *item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); virtual int EventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); virtual int EventEncounter(QuestEventID evt, std::string encounter_name, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); virtual bool HasQuestSub(uint32 npc_id, QuestEventID evt); virtual bool HasGlobalQuestSub(QuestEventID evt); @@ -65,25 +65,25 @@ public: virtual uint32 GetIdentifier() { return 0xb0712acc; } virtual int DispatchEventNPC(QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); virtual int DispatchEventPlayer(QuestEventID evt, Client *client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); virtual int DispatchEventItem(QuestEventID evt, Client *client, ItemInst *item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); virtual int DispatchEventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); private: int _EventNPC(std::string package_name, QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers, luabind::adl::object *l_func = nullptr); + std::vector *extra_pointers, luabind::adl::object *l_func = nullptr); int _EventPlayer(std::string package_name, QuestEventID evt, Client *client, std::string data, uint32 extra_data, - std::vector *extra_pointers, luabind::adl::object *l_func = nullptr); + std::vector *extra_pointers, luabind::adl::object *l_func = nullptr); int _EventItem(std::string package_name, QuestEventID evt, Client *client, ItemInst *item, Mob *mob, std::string data, - uint32 extra_data, std::vector *extra_pointers, luabind::adl::object *l_func = nullptr); + uint32 extra_data, std::vector *extra_pointers, luabind::adl::object *l_func = nullptr); int _EventSpell(std::string package_name, QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers, luabind::adl::object *l_func = nullptr); + std::vector *extra_pointers, luabind::adl::object *l_func = nullptr); int _EventEncounter(std::string package_name, QuestEventID evt, std::string encounter_name, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void LoadScript(std::string filename, std::string package_name); bool HasFunction(std::string function, std::string package_name); diff --git a/zone/lua_parser_events.cpp b/zone/lua_parser_events.cpp index 56e5d236d..87155d97b 100644 --- a/zone/lua_parser_events.cpp +++ b/zone/lua_parser_events.cpp @@ -27,7 +27,7 @@ //NPC void handle_npc_event_say(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { npc->DoQuestPause(init); Lua_Client l_client(reinterpret_cast(init)); @@ -43,7 +43,7 @@ void handle_npc_event_say(QuestInterface *parse, lua_State* L, NPC* npc, Mob *in } void handle_npc_event_trade(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { Lua_Client l_client(reinterpret_cast(init)); luabind::adl::object l_client_o = luabind::adl::object(L, l_client); l_client_o.push(L); @@ -56,7 +56,7 @@ void handle_npc_event_trade(QuestInterface *parse, lua_State* L, NPC* npc, Mob * if(extra_pointers) { for(size_t i = 0; i < extra_pointers->size(); ++i) { std::string prefix = "item" + std::to_string(static_cast(i + 1)); - Lua_ItemInst l_inst = reinterpret_cast(extra_pointers->at(i)); + Lua_ItemInst l_inst = EQEmu::any_cast(extra_pointers->at(i)); luabind::adl::object l_inst_o = luabind::adl::object(L, l_inst); l_inst_o.push(L); @@ -79,7 +79,7 @@ void handle_npc_event_trade(QuestInterface *parse, lua_State* L, NPC* npc, Mob * } void handle_npc_event_hp(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { if(extra_data == 1) { lua_pushinteger(L, -1); lua_setfield(L, -2, "hp_event"); @@ -96,7 +96,7 @@ void handle_npc_event_hp(QuestInterface *parse, lua_State* L, NPC* npc, Mob *ini } void handle_npc_single_mob(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { Lua_Mob l_mob(init); luabind::adl::object l_mob_o = luabind::adl::object(L, l_mob); l_mob_o.push(L); @@ -104,7 +104,7 @@ void handle_npc_single_mob(QuestInterface *parse, lua_State* L, NPC* npc, Mob *i } void handle_npc_single_client(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { Lua_Client l_client(reinterpret_cast(init)); luabind::adl::object l_client_o = luabind::adl::object(L, l_client); l_client_o.push(L); @@ -112,7 +112,7 @@ void handle_npc_single_client(QuestInterface *parse, lua_State* L, NPC* npc, Mob } void handle_npc_single_npc(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { Lua_NPC l_npc(reinterpret_cast(init)); luabind::adl::object l_npc_o = luabind::adl::object(L, l_npc); l_npc_o.push(L); @@ -120,7 +120,7 @@ void handle_npc_single_npc(QuestInterface *parse, lua_State* L, NPC* npc, Mob *i } void handle_npc_task_accepted(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { Lua_Client l_client(reinterpret_cast(init)); luabind::adl::object l_client_o = luabind::adl::object(L, l_client); l_client_o.push(L); @@ -131,7 +131,7 @@ void handle_npc_task_accepted(QuestInterface *parse, lua_State* L, NPC* npc, Mob } void handle_npc_popup(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { Lua_Mob l_mob(init); luabind::adl::object l_mob_o = luabind::adl::object(L, l_mob); l_mob_o.push(L); @@ -142,7 +142,7 @@ void handle_npc_popup(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, } void handle_npc_waypoint(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { Lua_Mob l_mob(init); luabind::adl::object l_mob_o = luabind::adl::object(L, l_mob); l_mob_o.push(L); @@ -153,7 +153,7 @@ void handle_npc_waypoint(QuestInterface *parse, lua_State* L, NPC* npc, Mob *ini } void handle_npc_hate(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { Lua_Mob l_mob(init); luabind::adl::object l_mob_o = luabind::adl::object(L, l_mob); l_mob_o.push(L); @@ -165,19 +165,19 @@ void handle_npc_hate(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, s void handle_npc_signal(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { lua_pushinteger(L, std::stoi(data)); lua_setfield(L, -2, "signal"); } void handle_npc_timer(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { lua_pushstring(L, data.c_str()); lua_setfield(L, -2, "timer"); } void handle_npc_death(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { Lua_Mob l_mob(init); luabind::adl::object l_mob_o = luabind::adl::object(L, l_mob); l_mob_o.push(L); @@ -205,7 +205,7 @@ void handle_npc_death(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, } void handle_npc_cast(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { int spell_id = std::stoi(data); if(IsValidSpell(spell_id)) { Lua_Spell l_spell(&spells[spell_id]); @@ -221,21 +221,21 @@ void handle_npc_cast(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, s } void handle_npc_area(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { - lua_pushinteger(L, *reinterpret_cast(extra_pointers->at(0))); + std::vector *extra_pointers) { + lua_pushinteger(L, *EQEmu::any_cast(extra_pointers->at(0))); lua_setfield(L, -2, "area_id"); - lua_pushinteger(L, *reinterpret_cast(extra_pointers->at(1))); + lua_pushinteger(L, *EQEmu::any_cast(extra_pointers->at(1))); lua_setfield(L, -2, "area_type"); } void handle_npc_null(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { } //Player void handle_player_say(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { lua_pushstring(L, data.c_str()); lua_setfield(L, -2, "message"); @@ -244,7 +244,7 @@ void handle_player_say(QuestInterface *parse, lua_State* L, Client* client, std: } void handle_player_death(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { Seperator sep(data.c_str()); Mob *o = entity_list.GetMobID(std::stoi(sep.arg[0])); @@ -274,13 +274,13 @@ void handle_player_death(QuestInterface *parse, lua_State* L, Client* client, st } void handle_player_timer(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { lua_pushstring(L, data.c_str()); lua_setfield(L, -2, "timer"); } void handle_player_discover_item(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { const Item_Struct *item = database.GetItem(extra_data); if(item) { Lua_Item l_item(item); @@ -296,51 +296,51 @@ void handle_player_discover_item(QuestInterface *parse, lua_State* L, Client* cl } void handle_player_fish_forage_success(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { - Lua_ItemInst l_item(reinterpret_cast(extra_pointers->at(0))); + std::vector *extra_pointers) { + Lua_ItemInst l_item(EQEmu::any_cast(extra_pointers->at(0))); luabind::adl::object l_item_o = luabind::adl::object(L, l_item); l_item_o.push(L); lua_setfield(L, -2, "item"); } void handle_player_click_object(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { - Lua_Object l_object(reinterpret_cast(extra_pointers->at(0))); + std::vector *extra_pointers) { + Lua_Object l_object(EQEmu::any_cast(extra_pointers->at(0))); luabind::adl::object l_object_o = luabind::adl::object(L, l_object); l_object_o.push(L); lua_setfield(L, -2, "object"); } void handle_player_click_door(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { - Lua_Door l_door(reinterpret_cast(extra_pointers->at(0))); + std::vector *extra_pointers) { + Lua_Door l_door(EQEmu::any_cast(extra_pointers->at(0))); luabind::adl::object l_door_o = luabind::adl::object(L, l_door); l_door_o.push(L); lua_setfield(L, -2, "door"); } void handle_player_signal(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { lua_pushinteger(L, std::stoi(data)); lua_setfield(L, -2, "signal"); } void handle_player_popup_response(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { lua_pushinteger(L, std::stoi(data)); lua_setfield(L, -2, "popup_id"); } void handle_player_pick_up(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { - Lua_ItemInst l_item(reinterpret_cast(extra_pointers->at(0))); + std::vector *extra_pointers) { + Lua_ItemInst l_item(EQEmu::any_cast(extra_pointers->at(0))); luabind::adl::object l_item_o = luabind::adl::object(L, l_item); l_item_o.push(L); lua_setfield(L, -2, "item"); } void handle_player_cast(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { int spell_id = std::stoi(data); if(IsValidSpell(spell_id)) { Lua_Spell l_spell(&spells[spell_id]); @@ -356,48 +356,48 @@ void handle_player_cast(QuestInterface *parse, lua_State* L, Client* client, std } void handle_player_task_fail(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { lua_pushinteger(L, std::stoi(data)); lua_setfield(L, -2, "task_id"); } void handle_player_zone(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { lua_pushinteger(L, std::stoi(data)); lua_setfield(L, -2, "zone_id"); } void handle_player_duel_win(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { - Lua_Client l_client(reinterpret_cast(extra_pointers->at(1))); + std::vector *extra_pointers) { + Lua_Client l_client(EQEmu::any_cast(extra_pointers->at(1))); luabind::adl::object l_client_o = luabind::adl::object(L, l_client); l_client_o.push(L); lua_setfield(L, -2, "other"); } void handle_player_duel_loss(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { - Lua_Client l_client(reinterpret_cast(extra_pointers->at(0))); + std::vector *extra_pointers) { + Lua_Client l_client(EQEmu::any_cast(extra_pointers->at(0))); luabind::adl::object l_client_o = luabind::adl::object(L, l_client); l_client_o.push(L); lua_setfield(L, -2, "other"); } void handle_player_loot(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { - Lua_ItemInst l_item(reinterpret_cast(extra_pointers->at(0))); + std::vector *extra_pointers) { + Lua_ItemInst l_item(EQEmu::any_cast(extra_pointers->at(0))); luabind::adl::object l_item_o = luabind::adl::object(L, l_item); l_item_o.push(L); lua_setfield(L, -2, "item"); - Lua_Corpse l_corpse(reinterpret_cast(extra_pointers->at(1))); + Lua_Corpse l_corpse(EQEmu::any_cast(extra_pointers->at(1))); luabind::adl::object l_corpse_o = luabind::adl::object(L, l_corpse); l_corpse_o.push(L); lua_setfield(L, -2, "corpse"); } void handle_player_task_stage_complete(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { Seperator sep(data.c_str()); lua_pushinteger(L, std::stoi(sep.arg[0])); lua_setfield(L, -2, "task_id"); @@ -407,7 +407,7 @@ void handle_player_task_stage_complete(QuestInterface *parse, lua_State* L, Clie } void handle_player_task_update(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { Seperator sep(data.c_str()); lua_pushinteger(L, std::stoi(sep.arg[0])); lua_setfield(L, -2, "count"); @@ -420,7 +420,7 @@ void handle_player_task_update(QuestInterface *parse, lua_State* L, Client* clie } void handle_player_command(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { Seperator sep(data.c_str(), ' ', 10, 100, true); std::string command(sep.arg[0] + 1); lua_pushstring(L, command.c_str()); @@ -439,7 +439,7 @@ void handle_player_command(QuestInterface *parse, lua_State* L, Client* client, } void handle_player_combine(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { lua_pushinteger(L, extra_data); lua_setfield(L, -2, "recipe_id"); @@ -448,24 +448,24 @@ void handle_player_combine(QuestInterface *parse, lua_State* L, Client* client, } void handle_player_feign(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { - Lua_NPC l_npc(reinterpret_cast(extra_pointers->at(0))); + std::vector *extra_pointers) { + Lua_NPC l_npc(EQEmu::any_cast(extra_pointers->at(0))); luabind::adl::object l_npc_o = luabind::adl::object(L, l_npc); l_npc_o.push(L); lua_setfield(L, -2, "other"); } void handle_player_area(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { - lua_pushinteger(L, *reinterpret_cast(extra_pointers->at(0))); + std::vector *extra_pointers) { + lua_pushinteger(L, *EQEmu::any_cast(extra_pointers->at(0))); lua_setfield(L, -2, "area_id"); - lua_pushinteger(L, *reinterpret_cast(extra_pointers->at(1))); + lua_pushinteger(L, *EQEmu::any_cast(extra_pointers->at(1))); lua_setfield(L, -2, "area_type"); } void handle_player_respawn(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { lua_pushinteger(L, std::stoi(data)); lua_setfield(L, -2, "option"); @@ -474,8 +474,8 @@ void handle_player_respawn(QuestInterface *parse, lua_State* L, Client* client, } void handle_player_packet(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { - Lua_Packet l_packet(reinterpret_cast(extra_pointers->at(0))); + std::vector *extra_pointers) { + Lua_Packet l_packet(EQEmu::any_cast(extra_pointers->at(0))); luabind::adl::object l_packet_o = luabind::adl::object(L, l_packet); l_packet_o.push(L); lua_setfield(L, -2, "packet"); @@ -485,24 +485,24 @@ void handle_player_packet(QuestInterface *parse, lua_State* L, Client* client, s } void handle_player_null(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { } //Item void handle_item_click(QuestInterface *parse, lua_State* L, Client* client, ItemInst* item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { lua_pushinteger(L, extra_data); lua_setfield(L, -2, "slot_id"); } void handle_item_timer(QuestInterface *parse, lua_State* L, Client* client, ItemInst* item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { lua_pushstring(L, data.c_str()); lua_setfield(L, -2, "timer"); } void handle_item_proc(QuestInterface *parse, lua_State* L, Client* client, ItemInst* item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { Lua_Mob l_mob(mob); luabind::adl::object l_mob_o = luabind::adl::object(L, l_mob); @@ -523,7 +523,7 @@ void handle_item_proc(QuestInterface *parse, lua_State* L, Client* client, ItemI } void handle_item_loot(QuestInterface *parse, lua_State* L, Client* client, ItemInst* item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { if(mob && mob->IsCorpse()) { Lua_Corpse l_corpse(mob->CastToCorpse()); luabind::adl::object l_corpse_o = luabind::adl::object(L, l_corpse); @@ -538,14 +538,14 @@ void handle_item_loot(QuestInterface *parse, lua_State* L, Client* client, ItemI } void handle_item_equip(QuestInterface *parse, lua_State* L, Client* client, ItemInst* item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { lua_pushinteger(L, extra_data); lua_setfield(L, -2, "slot_id"); } void handle_item_augment(QuestInterface *parse, lua_State* L, Client* client, ItemInst* item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers) { - Lua_ItemInst l_item(reinterpret_cast(extra_pointers->at(0))); + std::vector *extra_pointers) { + Lua_ItemInst l_item(EQEmu::any_cast(extra_pointers->at(0))); luabind::adl::object l_item_o = luabind::adl::object(L, l_item); l_item_o.push(L); lua_setfield(L, -2, "aug"); @@ -555,8 +555,8 @@ void handle_item_augment(QuestInterface *parse, lua_State* L, Client* client, It } void handle_item_augment_insert(QuestInterface *parse, lua_State* L, Client* client, ItemInst* item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers) { - Lua_ItemInst l_item(reinterpret_cast(extra_pointers->at(0))); + std::vector *extra_pointers) { + Lua_ItemInst l_item(EQEmu::any_cast(extra_pointers->at(0))); luabind::adl::object l_item_o = luabind::adl::object(L, l_item); l_item_o.push(L); lua_setfield(L, -2, "item"); @@ -566,8 +566,8 @@ void handle_item_augment_insert(QuestInterface *parse, lua_State* L, Client* cli } void handle_item_augment_remove(QuestInterface *parse, lua_State* L, Client* client, ItemInst* item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers) { - Lua_ItemInst l_item(reinterpret_cast(extra_pointers->at(0))); + std::vector *extra_pointers) { + Lua_ItemInst l_item(EQEmu::any_cast(extra_pointers->at(0))); luabind::adl::object l_item_o = luabind::adl::object(L, l_item); l_item_o.push(L); lua_setfield(L, -2, "item"); @@ -575,17 +575,17 @@ void handle_item_augment_remove(QuestInterface *parse, lua_State* L, Client* cli lua_pushinteger(L, extra_data); lua_setfield(L, -2, "slot_id"); - lua_pushboolean(L, *reinterpret_cast(extra_pointers->at(1))); + lua_pushboolean(L, *EQEmu::any_cast(extra_pointers->at(1))); lua_setfield(L, -2, "destroyed"); } void handle_item_null(QuestInterface *parse, lua_State* L, Client* client, ItemInst* item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { } //Spell void handle_spell_effect(QuestInterface *parse, lua_State* L, NPC* npc, Client* client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { if(npc) { Lua_Mob l_npc(npc); luabind::adl::object l_npc_o = luabind::adl::object(L, l_npc); @@ -602,7 +602,7 @@ void handle_spell_effect(QuestInterface *parse, lua_State* L, NPC* npc, Client* lua_setfield(L, -2, "target"); - lua_pushinteger(L, *reinterpret_cast(extra_pointers->at(0))); + lua_pushinteger(L, *EQEmu::any_cast(extra_pointers->at(0))); lua_setfield(L, -2, "buff_slot"); lua_pushinteger(L, extra_data); @@ -610,7 +610,7 @@ void handle_spell_effect(QuestInterface *parse, lua_State* L, NPC* npc, Client* } void handle_spell_tic(QuestInterface *parse, lua_State* L, NPC* npc, Client* client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { if(npc) { Lua_Mob l_npc(npc); luabind::adl::object l_npc_o = luabind::adl::object(L, l_npc); @@ -627,13 +627,13 @@ void handle_spell_tic(QuestInterface *parse, lua_State* L, NPC* npc, Client* cli lua_setfield(L, -2, "target"); - lua_pushinteger(L, *reinterpret_cast(extra_pointers->at(0))); + lua_pushinteger(L, *EQEmu::any_cast(extra_pointers->at(0))); lua_setfield(L, -2, "tics_remaining"); - lua_pushinteger(L, *reinterpret_cast(extra_pointers->at(1))); + lua_pushinteger(L, *EQEmu::any_cast(extra_pointers->at(1))); lua_setfield(L, -2, "caster_level"); - lua_pushinteger(L, *reinterpret_cast(extra_pointers->at(2))); + lua_pushinteger(L, *EQEmu::any_cast(extra_pointers->at(2))); lua_setfield(L, -2, "buff_slot"); lua_pushinteger(L, extra_data); @@ -641,7 +641,7 @@ void handle_spell_tic(QuestInterface *parse, lua_State* L, NPC* npc, Client* cli } void handle_spell_fade(QuestInterface *parse, lua_State* L, NPC* npc, Client* client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { if(npc) { Lua_Mob l_npc(npc); luabind::adl::object l_npc_o = luabind::adl::object(L, l_npc); @@ -661,12 +661,12 @@ void handle_spell_fade(QuestInterface *parse, lua_State* L, NPC* npc, Client* cl lua_pushinteger(L, extra_data); lua_setfield(L, -2, "buff_slot"); - lua_pushinteger(L, *reinterpret_cast(extra_pointers->at(0))); + lua_pushinteger(L, *EQEmu::any_cast(extra_pointers->at(0))); lua_setfield(L, -2, "caster_id"); } void handle_translocate_finish(QuestInterface *parse, lua_State* L, NPC* npc, Client* client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { if(npc) { Lua_Mob l_npc(npc); luabind::adl::object l_npc_o = luabind::adl::object(L, l_npc); @@ -685,7 +685,7 @@ void handle_translocate_finish(QuestInterface *parse, lua_State* L, NPC* npc, Cl } void handle_spell_null(QuestInterface *parse, lua_State* L, NPC* npc, Client* client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { } #endif diff --git a/zone/lua_parser_events.h b/zone/lua_parser_events.h index 8272e30c3..32a59e0da 100644 --- a/zone/lua_parser_events.h +++ b/zone/lua_parser_events.h @@ -2,128 +2,128 @@ #define _EQE_LUA_PARSER_EVENTS_H #ifdef LUA_EQEMU -typedef void(*NPCArgumentHandler)(QuestInterface*, lua_State*, NPC*, Mob*, std::string, uint32, std::vector*); -typedef void(*PlayerArgumentHandler)(QuestInterface*, lua_State*, Client*, std::string, uint32, std::vector*); -typedef void(*ItemArgumentHandler)(QuestInterface*, lua_State*, Client*, ItemInst*, Mob*, std::string, uint32, std::vector*); -typedef void(*SpellArgumentHandler)(QuestInterface*, lua_State*, NPC*, Client*, uint32, uint32, std::vector*); +typedef void(*NPCArgumentHandler)(QuestInterface*, lua_State*, NPC*, Mob*, std::string, uint32, std::vector*); +typedef void(*PlayerArgumentHandler)(QuestInterface*, lua_State*, Client*, std::string, uint32, std::vector*); +typedef void(*ItemArgumentHandler)(QuestInterface*, lua_State*, Client*, ItemInst*, Mob*, std::string, uint32, std::vector*); +typedef void(*SpellArgumentHandler)(QuestInterface*, lua_State*, NPC*, Client*, uint32, uint32, std::vector*); //NPC void handle_npc_event_say(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_npc_event_trade(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_npc_event_hp(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_npc_single_mob(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_npc_single_client(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_npc_single_npc(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_npc_task_accepted(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_npc_popup(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_npc_waypoint(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_npc_hate(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_npc_signal(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_npc_timer(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_npc_death(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_npc_cast(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_npc_area(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_npc_null(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); //Player void handle_player_say(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_player_death(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_player_timer(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_player_discover_item(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_player_fish_forage_success(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_player_click_object(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_player_click_door(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_player_signal(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_player_popup_response(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_player_pick_up(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_player_cast(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_player_task_fail(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_player_zone(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_player_duel_win(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_player_duel_loss(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_player_loot(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_player_task_stage_complete(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_player_task_update(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_player_command(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_player_combine(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_player_feign(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_player_area(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_player_respawn(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_player_packet(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_player_null(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); //Item void handle_item_click(QuestInterface *parse, lua_State* L, Client* client, ItemInst* item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_item_timer(QuestInterface *parse, lua_State* L, Client* client, ItemInst* item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_item_proc(QuestInterface *parse, lua_State* L, Client* client, ItemInst* item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_item_loot(QuestInterface *parse, lua_State* L, Client* client, ItemInst* item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_item_equip(QuestInterface *parse, lua_State* L, Client* client, ItemInst* item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_item_augment(QuestInterface *parse, lua_State* L, Client* client, ItemInst* item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_item_augment_insert(QuestInterface *parse, lua_State* L, Client* client, ItemInst* item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_item_augment_remove(QuestInterface *parse, lua_State* L, Client* client, ItemInst* item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_item_null(QuestInterface *parse, lua_State* L, Client* client, ItemInst* item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); //Spell void handle_spell_effect(QuestInterface *parse, lua_State* L, NPC* npc, Client* client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_spell_tic(QuestInterface *parse, lua_State* L, NPC* npc, Client* client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_spell_fade(QuestInterface *parse, lua_State* L, NPC* npc, Client* client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_translocate_finish(QuestInterface *parse, lua_State* L, NPC* npc, Client* client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); void handle_spell_null(QuestInterface *parse, lua_State* L, NPC* npc, Client* client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); #endif #endif diff --git a/zone/mob.cpp b/zone/mob.cpp index f9cdba950..0d7002253 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -183,6 +183,7 @@ Mob::Mob(const char* in_name, has_MGB = false; has_ProjectIllusion = false; SpellPowerDistanceMod = 0; + last_los_check = false; if(in_aa_title>0) aa_title = in_aa_title; @@ -341,6 +342,7 @@ Mob::Mob(const char* in_name, viral_spells[i] = 0; } pStandingPetOrder = SPO_Follow; + pseudo_rooted = false; see_invis = in_see_invis; see_invis_undead = in_see_invis_undead != 0; diff --git a/zone/mob.h b/zone/mob.h index fe0919f7e..55a57892b 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -464,6 +464,8 @@ public: bool CheckLosFN(float posX, float posY, float posZ, float mobSize); inline void SetChanged() { pLastChange = Timer::GetCurrentTime(); } inline const uint32 LastChange() const { return pLastChange; } + inline void SetLastLosState(bool value) { last_los_check = value; } + inline bool CheckLastLosState() const { return last_los_check; } //Quest void QuestReward(Client *c = nullptr, uint32 silver = 0, uint32 gold = 0, uint32 platinum = 0); @@ -752,7 +754,8 @@ public: inline const bool IsRooted() const { return rooted || permarooted; } inline const bool HasVirus() const { return has_virus; } int GetSnaredAmount(); - + inline const bool IsPseudoRooted() const { return pseudo_rooted; } + inline void SetPseudoRoot(bool prState) { pseudo_rooted = prState; } int GetCurWp() { return cur_wp; } @@ -1119,6 +1122,8 @@ protected: bool has_MGB; bool has_ProjectIllusion; int16 SpellPowerDistanceMod; + bool last_los_check; + bool pseudo_rooted; // Bind wound Timer bindwound_timer; diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index 87fc2ea79..10d41c78e 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -541,13 +541,40 @@ void NPC::AI_Start(uint32 iMoveDelay) { void Mob::AI_Stop() { if (!IsAIControlled()) return; + pAIControlled = false; + safe_delete(AIthink_timer); safe_delete(AIwalking_timer); safe_delete(AImovement_timer); - safe_delete(AItarget_check_timer) + safe_delete(AItarget_check_timer); safe_delete(AIscanarea_timer); safe_delete(AIfeignremember_timer); + safe_delete(PathingLOSCheckTimer); + safe_delete(PathingRouteUpdateTimerShort); + safe_delete(PathingRouteUpdateTimerLong); + + attack_timer.Disable(); + attack_dw_timer.Disable(); + ranged_timer.Disable(); + tic_timer.Disable(); + mana_timer.Disable(); + spellend_timer.Disable(); + projectile_timer.Disable(); + rewind_timer.Disable(); + bindwound_timer.Disable(); + stunned_timer.Disable(); + spun_timer.Disable(); + bardsong_timer.Disable(); + gravity_timer.Disable(); + viral_timer.Disable(); + flee_timer.Disable(); + + for (int sat = 0; sat < MAX_SPECIAL_ATTACK; ++sat) { + if (SpecialAbilities[sat].timer) + SpecialAbilities[sat].timer->Disable(); + } + hate_list.Wipe(); } diff --git a/zone/net.cpp b/zone/net.cpp index d81ea71e3..8a8421786 100644 --- a/zone/net.cpp +++ b/zone/net.cpp @@ -48,6 +48,7 @@ #include "worldserver.h" #include "net.h" #include "zone.h" +#include "queryserv.h" #include "command.h" #include "zone_config.h" #include "titles.h" @@ -98,6 +99,7 @@ npcDecayTimes_Struct npcCorpseDecayTimes[100]; TitleManager title_manager; DBAsyncFinishedQueue MTdbafq; DBAsync *dbasync = nullptr; +QueryServ *QServ = 0; TaskManager *taskmanager = 0; QuestParserCollection *parse = 0; @@ -114,6 +116,8 @@ int main(int argc, char** argv) { const char *zone_name; + QServ = new QueryServ; + if(argc == 3) { worldserver.SetLauncherName(argv[2]); worldserver.SetLaunchedName(argv[1]); @@ -622,7 +626,7 @@ void LoadSpells(EQEmu::MemoryMappedFile **mmf) { SPDAT_RECORDS = records; } - +/* Update Window Title with relevant information */ void UpdateWindowTitle(char* iNewTitle) { #ifdef _WINDOWS char tmp[500]; @@ -634,7 +638,7 @@ void UpdateWindowTitle(char* iNewTitle) { #if defined(GOTFRAGS) || defined(_EQDEBUG) snprintf(tmp, sizeof(tmp), "%i: %s, %i clients, %i", ZoneConfig::get()->ZonePort, zone->GetShortName(), numclients, getpid()); #else - snprintf(tmp, sizeof(tmp), "%i: %s, %i clients", ZoneConfig::get()->ZonePort, zone->GetShortName(), numclients); + snprintf(tmp, sizeof(tmp), "%s :: clients: %i inst_id: %i inst_ver: %i :: port: %i", zone->GetShortName(), numclients, zone->GetInstanceID(), zone->GetInstanceVersion(), ZoneConfig::get()->ZonePort); #endif } else { diff --git a/zone/npc.cpp b/zone/npc.cpp index f70c1e847..151852a2b 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -1787,252 +1787,49 @@ void NPC::ModifyNPCStat(const char *identifier, const char *newValue) { std::string id = identifier; std::string val = newValue; - for(int i = 0; i < id.length(); ++i) - { + for(int i = 0; i < id.length(); ++i) { id[i] = std::tolower(id[i]); } - if(id == "ac") - { - AC = atoi(val.c_str()); - return; - } - - if(id == "str") - { - STR = atoi(val.c_str()); - return; - } - - if(id == "sta") - { - STA = atoi(val.c_str()); - return; - } - - if(id == "agi") - { - AGI = atoi(val.c_str()); - return; - } - - if(id == "dex") - { - DEX = atoi(val.c_str()); - return; - } - - if(id == "wis") - { - WIS = atoi(val.c_str()); - CalcMaxMana(); - return; - } - - if(id == "int" || id == "_int") - { - INT = atoi(val.c_str()); - CalcMaxMana(); - return; - } - - if(id == "cha") - { - CHA = atoi(val.c_str()); - return; - } - - if(id == "max_hp") - { - base_hp = atoi(val.c_str()); - CalcMaxHP(); - if(cur_hp > max_hp) - cur_hp = max_hp; - return; - } - - if(id == "max_mana") - { - npc_mana = atoi(val.c_str()); - CalcMaxMana(); - if(cur_mana > max_mana) - cur_mana = max_mana; - return; - } - - if(id == "mr") - { - MR = atoi(val.c_str()); - return; - } - - if(id == "fr") - { - FR = atoi(val.c_str()); - return; - } - - if(id == "cr") - { - CR = atoi(val.c_str()); - return; - } - - if(id == "pr") - { - PR = atoi(val.c_str()); - return; - } - - if(id == "dr") - { - DR = atoi(val.c_str()); - return; - } - - if(id == "PhR") - { - PhR = atoi(val.c_str()); - return; - } - - if(id == "runspeed") - { - runspeed = (float)atof(val.c_str()); - CalcBonuses(); - return; - } - - if(id == "special_attacks") - { - //Added reset flag. - NPCSpecialAttacks(val.c_str(), 0, 1); - return; - } - - if(id == "attack_speed") - { - attack_speed = (float)atof(val.c_str()); - CalcBonuses(); - return; - } - - if(id == "atk") - { - ATK = atoi(val.c_str()); - return; - } - - if(id == "accuracy") - { - accuracy_rating = atoi(val.c_str()); - return; - } - - if(id == "avoidance") - { - avoidance_rating = atoi(val.c_str()); - return; - } - - if(id == "trackable") - { - trackable = atoi(val.c_str()); - return; - } - - if(id == "min_hit") - { - min_dmg = atoi(val.c_str()); - return; - } - - if(id == "max_hit") - { - max_dmg = atoi(val.c_str()); - return; - } - - if(id == "attack_count") - { - attack_count = atoi(val.c_str()); - return; - } - - if(id == "see_invis") - { - see_invis = atoi(val.c_str()); - return; - } - - if(id == "see_invis_undead") - { - see_invis_undead = atoi(val.c_str()); - return; - } - - if(id == "see_hide") - { - see_hide = atoi(val.c_str()); - return; - } - - if(id == "see_improved_hide") - { - see_improved_hide = atoi(val.c_str()); - return; - } - - if(id == "hp_regen") - { - hp_regen = atoi(val.c_str()); - return; - } - - if(id == "mana_regen") - { - mana_regen = atoi(val.c_str()); - return; - } - - if(id == "level") - { - SetLevel(atoi(val.c_str())); - return; - } - - if(id == "aggro") - { - pAggroRange = atof(val.c_str()); - return; - } - - if(id == "assist") - { - pAssistRange = atof(val.c_str()); - return; - } - - if(id == "slow_mitigation") - { - slow_mitigation = atoi(val.c_str()); - return; - } - if(id == "loottable_id") - { - loottable_id = atof(val.c_str()); - return; - } - if(id == "healscale") - { - healscale = atof(val.c_str()); - return; - } - if(id == "spellscale") - { - spellscale = atof(val.c_str()); - return; - } + if(id == "ac") { AC = atoi(val.c_str()); return; } + else if(id == "str") { STR = atoi(val.c_str()); return; } + else if(id == "sta") { STA = atoi(val.c_str()); return; } + else if(id == "agi") { AGI = atoi(val.c_str()); return; } + else if(id == "dex") { DEX = atoi(val.c_str()); return; } + else if(id == "wis") { WIS = atoi(val.c_str()); CalcMaxMana(); return; } + else if(id == "int" || id == "_int") { INT = atoi(val.c_str()); CalcMaxMana(); return; } + else if(id == "cha") { CHA = atoi(val.c_str()); return; } + else if(id == "max_hp") { base_hp = atoi(val.c_str()); CalcMaxHP(); if (cur_hp > max_hp) { cur_hp = max_hp; } return; } + else if(id == "max_mana") { npc_mana = atoi(val.c_str()); CalcMaxMana(); if (cur_mana > max_mana){ cur_mana = max_mana; } return; } + else if(id == "mr") { MR = atoi(val.c_str()); return; } + else if(id == "fr") { FR = atoi(val.c_str()); return; } + else if(id == "cr") { CR = atoi(val.c_str()); return; } + else if(id == "pr") { PR = atoi(val.c_str()); return; } + else if(id == "dr") { DR = atoi(val.c_str()); return; } + else if(id == "PhR") { PhR = atoi(val.c_str()); return; } + else if(id == "runspeed") { runspeed = (float)atof(val.c_str()); CalcBonuses(); return; } + else if(id == "special_attacks") { NPCSpecialAttacks(val.c_str(), 0, 1); return; } + else if(id == "attack_speed") { attack_speed = (float)atof(val.c_str()); CalcBonuses(); return; } + else if(id == "atk") { ATK = atoi(val.c_str()); return; } + else if(id == "accuracy") { accuracy_rating = atoi(val.c_str()); return; } + else if(id == "avoidance") { avoidance_rating = atoi(val.c_str()); return; } + else if(id == "trackable") { trackable = atoi(val.c_str()); return; } + else if(id == "min_hit") { min_dmg = atoi(val.c_str()); return; } + else if(id == "max_hit") { max_dmg = atoi(val.c_str()); return; } + else if(id == "attack_count") { attack_count = atoi(val.c_str()); return; } + else if(id == "see_invis") { see_invis = atoi(val.c_str()); return; } + else if(id == "see_invis_undead") { see_invis_undead = atoi(val.c_str()); return; } + else if(id == "see_hide") { see_hide = atoi(val.c_str()); return; } + else if(id == "see_improved_hide") { see_improved_hide = atoi(val.c_str()); return; } + else if(id == "hp_regen") { hp_regen = atoi(val.c_str()); return; } + else if(id == "mana_regen") { mana_regen = atoi(val.c_str()); return; } + else if(id == "level") { SetLevel(atoi(val.c_str())); return; } + else if(id == "aggro") { pAggroRange = atof(val.c_str()); return; } + else if(id == "assist") { pAssistRange = atof(val.c_str()); return; } + else if(id == "slow_mitigation") { slow_mitigation = atoi(val.c_str()); return; } + else if(id == "loottable_id") { loottable_id = atof(val.c_str()); return; } + else if(id == "healscale") { healscale = atof(val.c_str()); return; } + else if(id == "spellscale") { spellscale = atof(val.c_str()); return; } } void NPC::LevelScale() { diff --git a/zone/object.cpp b/zone/object.cpp index a2c418b40..c560191f1 100644 --- a/zone/object.cpp +++ b/zone/object.cpp @@ -465,7 +465,7 @@ bool Object::HandleClick(Client* sender, const ClickObject_Struct* click_object) char buf[10]; snprintf(buf, 9, "%u", m_inst->GetItem()->ID); buf[9] = '\0'; - std::vector args; + std::vector args; args.push_back(m_inst); parse->EventPlayer(EVENT_PLAYER_PICKUP, sender, buf, 0, &args); diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index 476c47599..72e2865c9 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -1320,6 +1320,8 @@ XS(XS_Client_MovePCInstance) else _log(CLIENT__ERROR, "Perl(XS_Client_MovePCInstance) attempted to process an Unknown type reference"); + Perl_croak(aTHX_ "THIS is not of type Client"); + Perl_croak(aTHX_ "THIS is not of type Client"); } } diff --git a/zone/petitions.cpp b/zone/petitions.cpp index 4dce685ea..ba57fe7f2 100644 --- a/zone/petitions.cpp +++ b/zone/petitions.cpp @@ -300,8 +300,6 @@ void ZoneDatabase::RefreshPetitionsFromDB() newpet->SetSentTime2(atol(row[13])); newpet->SetGMText(row[14]); - std::cout << "Petition " << row[0] << " pettime = " << newpet->GetSentTime() << std::endl; - if (atoi(row[12]) == 1) newpet->SetCheckedOut(true); else diff --git a/zone/queryserv.cpp b/zone/queryserv.cpp new file mode 100644 index 000000000..ad7de0424 --- /dev/null +++ b/zone/queryserv.cpp @@ -0,0 +1,52 @@ +/* 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 +*/ + +#include "../common/debug.h" +#include "../common/servertalk.h" +#include "../common/string_util.h" +#include "queryserv.h" +#include "worldserver.h" +#include "net.h" + +#include + +extern WorldServer worldserver; +extern QueryServ* QServ; + +QueryServ::QueryServ(){ +} + +QueryServ::~QueryServ(){ +} + +void QueryServ::SendQuery(std::string Query) +{ + ServerPacket* pack = new ServerPacket(ServerOP_QSSendQuery, Query.length() + 5); + pack->WriteUInt32(Query.length()); /* Pack Query String Size so it can be dynamically broken out at queryserv */ + pack->WriteString(Query.c_str()); /* Query */ + worldserver.SendPacket(pack); + safe_delete(pack); +} + +void QueryServ::PlayerLogEvent(int Event_Type, int Character_ID, std::string Event_Desc) +{ + std::string query = StringFormat( + "INSERT INTO `qs_player_events` (event, char_id, event_desc, time) VALUES (%i, %i, '%s', UNIX_TIMESTAMP(now()))", + Event_Type, Character_ID, EscapeString(Event_Desc).c_str()); + SendQuery(query); +} diff --git a/zone/queryserv.h b/zone/queryserv.h new file mode 100644 index 000000000..8aafcafda --- /dev/null +++ b/zone/queryserv.h @@ -0,0 +1,35 @@ +#ifndef QUERYSERV_ZONE_H +#define QUERYSERV_ZONE_H + + +/* + enum PlayerGenericLogEventTypes + These Enums are for the generic logging table that are not complex and require more advanced logic +*/ + +enum PlayerGenericLogEventTypes { + Player_Log_Quest = 1, + Player_Log_Zoning, + Player_Log_Deaths, + Player_Log_Connect_State, + Player_Log_Levels, + Player_Log_Keyring_Addition, + Player_Log_QGlobal_Update, + Player_Log_Task_Updates, + Player_Log_AA_Purchases, + Player_Log_Trade_Skill_Events, + Player_Log_Issued_Commands, + Player_Log_Money_Transactions, + Player_Log_Alternate_Currency_Transactions, +}; + + +class QueryServ{ + public: + QueryServ(); + ~QueryServ(); + void SendQuery(std::string Query); + void PlayerLogEvent(int Event_Type, int Character_ID, std::string Event_Desc); +}; + +#endif /* QUERYSERV_ZONE_H */ \ No newline at end of file diff --git a/zone/quest_interface.h b/zone/quest_interface.h index 8e3e3a93e..68c79923f 100644 --- a/zone/quest_interface.h +++ b/zone/quest_interface.h @@ -20,6 +20,7 @@ #define _EQE_QUESTINTERFACE_H #include "../common/types.h" +#include "../common/any.h" #include "event_codes.h" class ItemInst; @@ -29,19 +30,19 @@ class NPC; class QuestInterface { public: virtual int EventNPC(QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { return 0; } + std::vector *extra_pointers) { return 0; } virtual int EventGlobalNPC(QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { return 0; } + std::vector *extra_pointers) { return 0; } virtual int EventPlayer(QuestEventID evt, Client *client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { return 0; } + std::vector *extra_pointers) { return 0; } virtual int EventGlobalPlayer(QuestEventID evt, Client *client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { return 0; } + std::vector *extra_pointers) { return 0; } virtual int EventItem(QuestEventID evt, Client *client, ItemInst *item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers) { return 0; } + std::vector *extra_pointers) { return 0; } virtual int EventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers) { return 0; } + std::vector *extra_pointers) { return 0; } virtual int EventEncounter(QuestEventID evt, std::string encounter_name, uint32 extra_data, - std::vector *extra_pointers) { return 0; } + std::vector *extra_pointers) { return 0; } virtual bool HasQuestSub(uint32 npcid, QuestEventID evt) { return false; } virtual bool HasGlobalQuestSub(QuestEventID evt) { return false; } @@ -60,13 +61,13 @@ public: virtual void LoadEncounterScript(std::string filename, std::string encounter_name) { } virtual int DispatchEventNPC(QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { return 0; } + std::vector *extra_pointers) { return 0; } virtual int DispatchEventPlayer(QuestEventID evt, Client *client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { return 0; } + std::vector *extra_pointers) { return 0; } virtual int DispatchEventItem(QuestEventID evt, Client *client, ItemInst *item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers) { return 0; } + std::vector *extra_pointers) { return 0; } virtual int DispatchEventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers) { return 0; } + std::vector *extra_pointers) { return 0; } virtual void AddVar(std::string name, std::string val) { } virtual std::string GetVar(std::string name) { return std::string(); } diff --git a/zone/quest_parser_collection.cpp b/zone/quest_parser_collection.cpp index 376c121d3..10703c299 100644 --- a/zone/quest_parser_collection.cpp +++ b/zone/quest_parser_collection.cpp @@ -234,7 +234,7 @@ bool QuestParserCollection::ItemHasQuestSub(ItemInst *itm, QuestEventID evt) { } int QuestParserCollection::EventNPC(QuestEventID evt, NPC *npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { int rd = DispatchEventNPC(evt, npc, init, data, extra_data, extra_pointers); int rl = EventNPCLocal(evt, npc, init, data, extra_data, extra_pointers); int rg = EventNPCGlobal(evt, npc, init, data, extra_data, extra_pointers); @@ -252,7 +252,7 @@ int QuestParserCollection::EventNPC(QuestEventID evt, NPC *npc, Mob *init, std:: } int QuestParserCollection::EventNPCLocal(QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { std::map::iterator iter = _npc_quest_status.find(npc->GetNPCTypeID()); if(iter != _npc_quest_status.end()) { //loaded or failed to load @@ -275,7 +275,7 @@ int QuestParserCollection::EventNPCLocal(QuestEventID evt, NPC* npc, Mob *init, } int QuestParserCollection::EventNPCGlobal(QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { if(_global_npc_quest_status != QuestUnloaded && _global_npc_quest_status != QuestFailedToLoad) { std::map::iterator qiter = _interfaces.find(_global_npc_quest_status); return qiter->second->EventGlobalNPC(evt, npc, init, data, extra_data, extra_pointers); @@ -294,7 +294,7 @@ int QuestParserCollection::EventNPCGlobal(QuestEventID evt, NPC* npc, Mob *init, } int QuestParserCollection::EventPlayer(QuestEventID evt, Client *client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { int rd = DispatchEventPlayer(evt, client, data, extra_data, extra_pointers); int rl = EventPlayerLocal(evt, client, data, extra_data, extra_pointers); int rg = EventPlayerGlobal(evt, client, data, extra_data, extra_pointers); @@ -312,7 +312,7 @@ int QuestParserCollection::EventPlayer(QuestEventID evt, Client *client, std::st } int QuestParserCollection::EventPlayerLocal(QuestEventID evt, Client *client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { if(_player_quest_status == QuestUnloaded) { std::string filename; QuestInterface *qi = GetQIByPlayerQuest(filename); @@ -331,7 +331,7 @@ int QuestParserCollection::EventPlayerLocal(QuestEventID evt, Client *client, st } int QuestParserCollection::EventPlayerGlobal(QuestEventID evt, Client *client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { if(_global_player_quest_status == QuestUnloaded) { std::string filename; QuestInterface *qi = GetQIByGlobalPlayerQuest(filename); @@ -350,7 +350,7 @@ int QuestParserCollection::EventPlayerGlobal(QuestEventID evt, Client *client, s } int QuestParserCollection::EventItem(QuestEventID evt, Client *client, ItemInst *item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { std::string item_script; if(item->GetItem()->ScriptFileID != 0) { item_script = "script_"; @@ -396,7 +396,7 @@ int QuestParserCollection::EventItem(QuestEventID evt, Client *client, ItemInst } int QuestParserCollection::EventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { std::map::iterator iter = _spell_quest_status.find(spell_id); if(iter != _spell_quest_status.end()) { //loaded or failed to load @@ -431,7 +431,7 @@ int QuestParserCollection::EventSpell(QuestEventID evt, NPC* npc, Client *client } int QuestParserCollection::EventEncounter(QuestEventID evt, std::string encounter_name, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { auto iter = _encounter_quest_status.find(encounter_name); if(iter != _encounter_quest_status.end()) { //loaded or failed to load @@ -600,9 +600,8 @@ QuestInterface *QuestParserCollection::GetQIByNPCQuest(uint32 npcid, std::string } QuestInterface *QuestParserCollection::GetQIByPlayerQuest(std::string &filename) { - - if(!zone) - return nullptr; + if(!zone || !zone->IsLoaded()) + return nullptr; //first look for /quests/zone/player_v[instance_version].ext (precedence) filename = "quests/"; @@ -975,7 +974,7 @@ void QuestParserCollection::GetErrors(std::list &err) { } int QuestParserCollection::DispatchEventNPC(QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { int ret = 0; auto iter = _load_precedence.begin(); while(iter != _load_precedence.end()) { @@ -989,7 +988,7 @@ int QuestParserCollection::DispatchEventNPC(QuestEventID evt, NPC* npc, Mob *ini } int QuestParserCollection::DispatchEventPlayer(QuestEventID evt, Client *client, std::string data, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { int ret = 0; auto iter = _load_precedence.begin(); while(iter != _load_precedence.end()) { @@ -1003,7 +1002,7 @@ int QuestParserCollection::DispatchEventPlayer(QuestEventID evt, Client *client, } int QuestParserCollection::DispatchEventItem(QuestEventID evt, Client *client, ItemInst *item, Mob *mob, std::string data, - uint32 extra_data, std::vector *extra_pointers) { + uint32 extra_data, std::vector *extra_pointers) { int ret = 0; auto iter = _load_precedence.begin(); while(iter != _load_precedence.end()) { @@ -1017,7 +1016,7 @@ int QuestParserCollection::DispatchEventItem(QuestEventID evt, Client *client, I } int QuestParserCollection::DispatchEventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers) { + std::vector *extra_pointers) { int ret = 0; auto iter = _load_precedence.begin(); while(iter != _load_precedence.end()) { diff --git a/zone/quest_parser_collection.h b/zone/quest_parser_collection.h index 902500231..006bcfe24 100644 --- a/zone/quest_parser_collection.h +++ b/zone/quest_parser_collection.h @@ -51,15 +51,15 @@ public: bool ItemHasQuestSub(ItemInst *itm, QuestEventID evt); int EventNPC(QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers = nullptr); + std::vector *extra_pointers = nullptr); int EventPlayer(QuestEventID evt, Client *client, std::string data, uint32 extra_data, - std::vector *extra_pointers = nullptr); + std::vector *extra_pointers = nullptr); int EventItem(QuestEventID evt, Client *client, ItemInst *item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers = nullptr); + std::vector *extra_pointers = nullptr); int EventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers = nullptr); + std::vector *extra_pointers = nullptr); int EventEncounter(QuestEventID evt, std::string encounter_name, uint32 extra_data, - std::vector *extra_pointers = nullptr); + std::vector *extra_pointers = nullptr); void GetErrors(std::list &err); @@ -69,10 +69,10 @@ private: bool PlayerHasQuestSubLocal(QuestEventID evt); bool PlayerHasQuestSubGlobal(QuestEventID evt); - int EventNPCLocal(QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, std::vector *extra_pointers); - int EventNPCGlobal(QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, std::vector *extra_pointers); - int EventPlayerLocal(QuestEventID evt, Client *client, std::string data, uint32 extra_data, std::vector *extra_pointers); - int EventPlayerGlobal(QuestEventID evt, Client *client, std::string data, uint32 extra_data, std::vector *extra_pointers); + int EventNPCLocal(QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, std::vector *extra_pointers); + int EventNPCGlobal(QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, std::vector *extra_pointers); + int EventPlayerLocal(QuestEventID evt, Client *client, std::string data, uint32 extra_data, std::vector *extra_pointers); + int EventPlayerGlobal(QuestEventID evt, Client *client, std::string data, uint32 extra_data, std::vector *extra_pointers); QuestInterface *GetQIByNPCQuest(uint32 npcid, std::string &filename); QuestInterface *GetQIByGlobalNPCQuest(std::string &filename); @@ -83,13 +83,13 @@ private: QuestInterface *GetQIByEncounterQuest(std::string encounter_name, std::string &filename); int DispatchEventNPC(QuestEventID evt, NPC* npc, Mob *init, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); int DispatchEventPlayer(QuestEventID evt, Client *client, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); int DispatchEventItem(QuestEventID evt, Client *client, ItemInst *item, Mob *mob, std::string data, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); int DispatchEventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, - std::vector *extra_pointers); + std::vector *extra_pointers); std::map _interfaces; std::map _extensions; diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index d8b9094be..fc31178ae 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -75,12 +75,12 @@ And then at then end of embparser.cpp, add: #include "../common/rulesys.h" #include "qglobals.h" #include "quest_parser_collection.h" - +#include "queryserv.h" #ifdef BOTS #include "bot.h" #endif - +extern QueryServ* QServ; extern Zone* zone; extern WorldServer worldserver; extern EntityList entity_list; @@ -172,8 +172,7 @@ void QuestManager::EndQuest() { cur = QTimerList.erase(cur); else ++cur; - } - + } run.owner->Depop(); } quests_running_.pop(); @@ -1294,33 +1293,29 @@ void QuestManager::setglobal(const char *varname, const char *newvalue, int opti int qgNpcid = owner->GetNPCTypeID(); /* options value determines the availability of global variables to NPCs when a quest begins - ------------------------------------------------------------------ - value npcid player zone - ------------------------------------------------------------------ - 0 this this this - 1 all this this - 2 this all this - 3 all all this - 4 this this all - 5 all this all - 6 this all all - 7 all all all + ------------------------------------------------------------------ + value npcid player zone + ------------------------------------------------------------------ + 0 this this this + 1 all this this + 2 this all this + 3 all all this + 4 this this all + 5 all this all + 6 this all all + 7 all all all */ - if (initiator && initiator->IsClient()) // some events like waypoint and spawn don't have a player involved - { - qgCharid=initiator->CharacterID(); - } - else - { + if (initiator && initiator->IsClient()){ // some events like waypoint and spawn don't have a player involved + qgCharid=initiator->CharacterID(); + } + else { qgCharid=-qgNpcid; // make char id negative npc id as a fudge } - if (options < 0 || options > 7) - { + if (options < 0 || options > 7) { std::cerr << "Invalid options for global var " << varname << " using defaults" << std::endl; } // default = 0 (only this npcid,player and zone) - else - { + else { if (options & 1) qgNpcid=0; if (options & 2) @@ -1330,30 +1325,32 @@ void QuestManager::setglobal(const char *varname, const char *newvalue, int opti } InsertQuestGlobal(qgCharid, qgNpcid, qgZoneid, varname, newvalue, QGVarDuration(duration)); + + /* QS: PlayerLogQGlobalUpdate */ + if (RuleB(QueryServ, PlayerLogQGlobalUpdate) && qgCharid && qgCharid > 0 && initiator && initiator->IsClient()){ + std::string event_desc = StringFormat("Update :: qglobal:%s to qvalue:%s zoneid:%i instid:%i", varname, newvalue, initiator->GetZoneID(), initiator->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_QGlobal_Update, qgCharid, event_desc); + } } /* Inserts global variable into quest_globals table */ -int QuestManager::InsertQuestGlobal( - int charid, int npcid, int zoneid, - const char *varname, const char *varvalue, - int duration) -{ +int QuestManager::InsertQuestGlobal(int charid, int npcid, int zoneid, const char *varname, const char *varvalue, int duration) { char *query = 0; char errbuf[MYSQL_ERRMSG_SIZE]; // Make duration string either "unix_timestamp(now()) + xxx" or "NULL" - std::stringstream duration_ss; - if (duration == INT_MAX) - { + std::stringstream duration_ss; + if (duration == INT_MAX) { duration_ss << "NULL"; } - else - { + else { duration_ss << "unix_timestamp(now()) + " << duration; } - //NOTE: this should be escaping the contents of arglist - //npcwise a malicious script can arbitrarily alter the DB + /* + NOTE: this should be escaping the contents of arglist + npcwise a malicious script can arbitrarily alter the DB + */ uint32 last_id = 0; if (!database.RunQuery(query, MakeAnyLenString(&query, "REPLACE INTO quest_globals (charid, npcid, zoneid, name, value, expdate)" @@ -1365,9 +1362,8 @@ int QuestManager::InsertQuestGlobal( } safe_delete_array(query); - if(zone) - { - //first delete our global + if(zone) { + /* Delete existing qglobal data and update zone processes */ ServerPacket* pack = new ServerPacket(ServerOP_QGlobalDelete, sizeof(ServerQGlobalDelete_Struct)); ServerQGlobalDelete_Struct *qgd = (ServerQGlobalDelete_Struct*)pack->pBuffer; qgd->npc_id = npcid; @@ -1383,18 +1379,16 @@ int QuestManager::InsertQuestGlobal( worldserver.SendPacket(pack); safe_delete(pack); - //then create a new one with the new id + /* Create new qglobal data and update zone processes */ pack = new ServerPacket(ServerOP_QGlobalUpdate, sizeof(ServerQGlobalUpdate_Struct)); ServerQGlobalUpdate_Struct *qgu = (ServerQGlobalUpdate_Struct*)pack->pBuffer; qgu->npc_id = npcid; qgu->char_id = charid; qgu->zone_id = zoneid; - if(duration == INT_MAX) - { + if(duration == INT_MAX) { qgu->expdate = 0xFFFFFFFF; } - else - { + else { qgu->expdate = Timer::GetTimeSeconds() + duration; } strcpy((char*)qgu->name, varname); @@ -1420,8 +1414,7 @@ int QuestManager::InsertQuestGlobal( return 0; } -void QuestManager::targlobal(const char *varname, const char *value, const char *duration, int qgNpcid, int qgCharid, int qgZoneid) -{ +void QuestManager::targlobal(const char *varname, const char *value, const char *duration, int qgNpcid, int qgCharid, int qgZoneid) { InsertQuestGlobal(qgCharid, qgNpcid, qgZoneid, varname, value, QGVarDuration(duration)); } @@ -1432,15 +1425,24 @@ void QuestManager::delglobal(const char *varname) { int qgZoneid=zone->GetZoneID(); int qgCharid=0; int qgNpcid=owner->GetNPCTypeID(); + + + if (initiator && initiator->IsClient()) // some events like waypoint and spawn don't have a player involved { qgCharid=initiator->CharacterID(); } - else - { + else { qgCharid=-qgNpcid; // make char id negative npc id as a fudge } + + /* QS: PlayerLogQGlobalUpdate */ + if (RuleB(QueryServ, PlayerLogQGlobalUpdate) && qgCharid && qgCharid > 0 && initiator && initiator->IsClient()){ + std::string event_desc = StringFormat("Deleted :: qglobal:%s zoneid:%i instid:%i", varname, initiator->GetZoneID(), initiator->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_QGlobal_Update, qgCharid, event_desc); + } + if (!database.RunQuery(query, MakeAnyLenString(&query, "DELETE FROM quest_globals WHERE name='%s'" @@ -1451,8 +1453,7 @@ void QuestManager::delglobal(const char *varname) { } safe_delete_array(query); - if(zone) - { + if(zone) { ServerPacket* pack = new ServerPacket(ServerOP_QGlobalDelete, sizeof(ServerQGlobalDelete_Struct)); ServerQGlobalDelete_Struct *qgu = (ServerQGlobalDelete_Struct*)pack->pBuffer; @@ -1701,17 +1702,14 @@ void QuestManager::showgrid(int grid) { pts.push_back(pt); // Retrieve all waypoints for this grid - if(database.RunQuery(query,MakeAnyLenString(&query,"SELECT `x`,`y`,`z` FROM grid_entries WHERE `gridid`=%i AND `zoneid`=%i ORDER BY `number`",grid,zone->GetZoneID()),errbuf,&result)) - { - while((row = mysql_fetch_row(result))) - { + if(database.RunQuery(query,MakeAnyLenString(&query,"SELECT `x`,`y`,`z` FROM grid_entries WHERE `gridid`=%i AND `zoneid`=%i ORDER BY `number`",grid,zone->GetZoneID()),errbuf,&result)) { + while((row = mysql_fetch_row(result))) { pt.x = atof(row[0]); pt.y = atof(row[1]); pt.z = atof(row[2]); pts.push_back(pt); } - mysql_free_result(result); - + mysql_free_result(result); initiator->SendPathPacket(pts); } else // DB query error! diff --git a/zone/spawn2.cpp b/zone/spawn2.cpp index dd541e0a2..25cbb8ccf 100644 --- a/zone/spawn2.cpp +++ b/zone/spawn2.cpp @@ -354,96 +354,86 @@ void Spawn2::DeathReset(bool realdeath) } bool ZoneDatabase::PopulateZoneSpawnList(uint32 zoneid, LinkedList &spawn2_list, int16 version, uint32 repopdelay) { - char errbuf[MYSQL_ERRMSG_SIZE]; - char* query = 0; - MYSQL_RES *result; - MYSQL_ROW row; const char *zone_name = database.GetZoneName(zoneid); - - MakeAnyLenString(&query, "SELECT id, spawngroupID, x, y, z, heading, respawntime, variance, pathgrid, _condition, cond_value, enabled, animation FROM spawn2 WHERE zone='%s' AND version=%u", zone_name, version); - if (RunQuery(query, strlen(query), errbuf, &result)) - { - safe_delete_array(query); - while((row = mysql_fetch_row(result))) - { - Spawn2* newSpawn = 0; - - bool perl_enabled = atoi(row[11]) == 1 ? true : false; - uint32 spawnLeft = (GetSpawnTimeLeft(atoi(row[0]), zone->GetInstanceID()) * 1000); - newSpawn = new Spawn2(atoi(row[0]), atoi(row[1]), atof(row[2]), atof(row[3]), atof(row[4]), atof(row[5]), atoi(row[6]), atoi(row[7]), spawnLeft, atoi(row[8]), atoi(row[9]), atoi(row[10]), perl_enabled, (EmuAppearance)atoi(row[12])); - spawn2_list.Insert( newSpawn ); - } - mysql_free_result(result); - } - else - { - LogFile->write(EQEMuLog::Error, "Error in PopulateZoneLists query '%s': %s", query, errbuf); - safe_delete_array(query); + std::string query = StringFormat("SELECT id, spawngroupID, x, y, z, heading, " + "respawntime, variance, pathgrid, _condition, " + "cond_value, enabled, animation FROM spawn2 " + "WHERE zone = '%s' AND version = %u", + zone_name, version); + auto results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in PopulateZoneLists query '%s': %s", query.c_str(), results.ErrorMessage().c_str()); return false; - } + } + + for (auto row = results.begin(); row != results.end(); ++row) { + Spawn2* newSpawn = 0; + + bool perl_enabled = atoi(row[11]) == 1? true: false; + uint32 spawnLeft = (GetSpawnTimeLeft(atoi(row[0]), zone->GetInstanceID()) * 1000); + newSpawn = new Spawn2(atoi(row[0]), atoi(row[1]), atof(row[2]), atof(row[3]), atof(row[4]), + atof(row[5]), atoi(row[6]), atoi(row[7]), spawnLeft, atoi(row[8]), + atoi(row[9]), atoi(row[10]), perl_enabled, (EmuAppearance)atoi(row[12])); + + spawn2_list.Insert(newSpawn); + } return true; } Spawn2* ZoneDatabase::LoadSpawn2(LinkedList &spawn2_list, uint32 spawn2id, uint32 timeleft) { - char errbuf[MYSQL_ERRMSG_SIZE]; - char* query = 0; - MYSQL_RES *result; - MYSQL_ROW row; - if (RunQuery(query, MakeAnyLenString(&query, "SELECT id, spawngroupID, x, y, z, heading, respawntime, variance, pathgrid, _condition, cond_value, enabled, animation FROM spawn2 WHERE id=%i", spawn2id), errbuf, &result)) { - if (mysql_num_rows(result) == 1) - { - row = mysql_fetch_row(result); - bool perl_enabled = atoi(row[11]) == 1 ? true : false; - Spawn2* newSpawn = new Spawn2(atoi(row[0]), atoi(row[1]), atof(row[2]), atof(row[3]), atof(row[4]), atof(row[5]), atoi(row[6]), atoi(row[7]), timeleft, atoi(row[8]), atoi(row[9]), atoi(row[10]), perl_enabled, (EmuAppearance)atoi(row[12])); - spawn2_list.Insert( newSpawn ); - mysql_free_result(result); - safe_delete_array(query); - return newSpawn; - } - mysql_free_result(result); - } + std::string query = StringFormat("SELECT id, spawngroupID, x, y, z, heading, " + "respawntime, variance, pathgrid, _condition, " + "cond_value, enabled, animation FROM spawn2 " + "WHERE id = %i", spawn2id); + auto results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in LoadSpawn2 query '%s': %s", query.c_str(), results.ErrorMessage().c_str()); + return nullptr; + } - LogFile->write(EQEMuLog::Error, "Error in LoadSpawn2 query '%s': %s", query, errbuf); - safe_delete_array(query); - return 0; + if (results.RowCount() != 1) { + LogFile->write(EQEMuLog::Error, "Error in LoadSpawn2 query '%s': %s", query.c_str(), results.ErrorMessage().c_str()); + return nullptr; + } + + auto row = results.begin(); + + bool perl_enabled = atoi(row[11]) == 1 ? true : false; + + Spawn2* newSpawn = new Spawn2(atoi(row[0]), atoi(row[1]), atof(row[2]), atof(row[3]), atof(row[4]), + atof(row[5]), atoi(row[6]), atoi(row[7]), timeleft, atoi(row[8]), + atoi(row[9]), atoi(row[10]), perl_enabled, (EmuAppearance)atoi(row[12])); + + spawn2_list.Insert(newSpawn); + + return newSpawn; } -bool ZoneDatabase::CreateSpawn2(Client *c, uint32 spawngroup, const char* zone, float heading, float x, float y, float z, uint32 respawn, uint32 variance, uint16 condition, int16 cond_value) +bool ZoneDatabase::CreateSpawn2(Client *client, uint32 spawngroup, const char* zone, float heading, float x, float y, float z, uint32 respawn, uint32 variance, uint16 condition, int16 cond_value) { - char errbuf[MYSQL_ERRMSG_SIZE]; - char *query = 0; - uint32 affected_rows = 0; - - // if(GetInverseXY()==1) { - // float temp=x; - // x=y; - // y=temp; - // } - if (RunQuery(query, MakeAnyLenString(&query, - "INSERT INTO spawn2 (spawngroupID,zone,x,y,z,heading,respawntime,variance,_condition,cond_value) Values (%i, '%s', %f, %f, %f, %f, %i, %i, %u, %i)", - spawngroup, zone, x, y, z, heading, respawn, variance, condition, cond_value - ), errbuf, 0, &affected_rows)) { - safe_delete_array(query); - if (affected_rows == 1) { - if(c) c->LogSQL(query); - return true; - } - else { - return false; - } - } - else { - LogFile->write(EQEMuLog::Error, "Error in CreateSpawn2 query '%s': %s", query, errbuf); - safe_delete_array(query); + std::string query = StringFormat("INSERT INTO spawn2 (spawngroupID, zone, x, y, z, heading, " + "respawntime, variance, _condition, cond_value) " + "VALUES (%i, '%s', %f, %f, %f, %f, %i, %i, %u, %i)", + spawngroup, zone, x, y, z, heading, + respawn, variance, condition, cond_value); + auto results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in CreateSpawn2 query '%s': %s", query.c_str(), results.ErrorMessage().c_str()); return false; - } + } - return false; + if (results.RowsAffected() != 1) + return false; + + if(client) + client->LogSQL(query.c_str()); + + return true; } uint32 Zone::CountSpawn2() { @@ -671,177 +661,159 @@ void SpawnConditionManager::ExecEvent(SpawnEvent &event, bool send_update) { } void SpawnConditionManager::UpdateDBEvent(SpawnEvent &event) { - char errbuf[MYSQL_ERRMSG_SIZE]; - char* query = 0; - int len; - SpawnCondition cond; - len = MakeAnyLenString(&query, - "UPDATE spawn_events SET " - "next_minute=%d, next_hour=%d, next_day=%d, next_month=%d, " - "next_year=%d, enabled=%d, strict=%d " - "WHERE id=%d", - event.next.minute, event.next.hour, event.next.day, event.next.month, - event.next.year, event.enabled?1:0, event.strict?1:0,event.id - ); - if(!database.RunQuery(query, len, errbuf)) { - LogFile->write(EQEMuLog::Error, "Unable to update spawn event '%s': %s\n", query, errbuf); - } - safe_delete_array(query); + std::string query = StringFormat("UPDATE spawn_events SET " + "next_minute = %d, next_hour = %d, " + "next_day = %d, next_month = %d, " + "next_year = %d, enabled = %d, " + "strict = %d WHERE id = %d", + event.next.minute, event.next.hour, + event.next.day, event.next.month, + event.next.year, event.enabled? 1: 0, + event.strict? 1: 0, event.id); + auto results = database.QueryDatabase(query); + if(!results.Success()) + LogFile->write(EQEMuLog::Error, "Unable to update spawn event '%s': %s\n", query.c_str(), results.ErrorMessage().c_str()); + } void SpawnConditionManager::UpdateDBCondition(const char* zone_name, uint32 instance_id, uint16 cond_id, int16 value) { - char errbuf[MYSQL_ERRMSG_SIZE]; - char* query = 0; - int len; - SpawnCondition cond; - len = MakeAnyLenString(&query, - "REPLACE INTO spawn_condition_values (id, value, zone, instance_id) VALUES(%u, %u, '%s', %u)", - cond_id, value, zone_name, instance_id - ); - if(!database.RunQuery(query, len, errbuf)) { - LogFile->write(EQEMuLog::Error, "Unable to update spawn condition '%s': %s\n", query, errbuf); - } - safe_delete_array(query); + std::string query = StringFormat("REPLACE INTO spawn_condition_values " + "(id, value, zone, instance_id) " + "VALUES( %u, %u, '%s', %u)", + cond_id, value, zone_name, instance_id); + auto results = database.QueryDatabase(query); + if(!results.Success()) + LogFile->write(EQEMuLog::Error, "Unable to update spawn condition '%s': %s\n", query.c_str(), results.ErrorMessage().c_str()); + } bool SpawnConditionManager::LoadDBEvent(uint32 event_id, SpawnEvent &event, std::string &zone_name) { - char errbuf[MYSQL_ERRMSG_SIZE]; - char* query = 0; - MYSQL_RES *result; - MYSQL_ROW row; - int len; - bool ret = false; - - len = MakeAnyLenString(&query, - "SELECT id,cond_id,period,next_minute,next_hour,next_day,next_month,next_year,enabled,action,argument,strict,zone " - "FROM spawn_events WHERE id=%d", event_id); - if (database.RunQuery(query, len, errbuf, &result)) { - safe_delete_array(query); - if((row = mysql_fetch_row(result))) { - event.id = atoi(row[0]); - event.condition_id = atoi(row[1]); - event.period = atoi(row[2]); - - event.next.minute = atoi(row[3]); - event.next.hour = atoi(row[4]); - event.next.day = atoi(row[5]); - event.next.month = atoi(row[6]); - event.next.year = atoi(row[7]); - - event.enabled = atoi(row[8])==0?false:true; - event.action = (SpawnEvent::Action) atoi(row[9]); - event.argument = atoi(row[10]); - event.strict = atoi(row[11])==0?false:true; - zone_name = row[12]; - - std::string t; - EQTime::ToString(&event.next, t); - _log(SPAWNS__CONDITIONS, "(LoadDBEvent) Loaded %s spawn event %d on condition %d with period %d, action %d, argument %d, strict %d. Will trigger at %s", - event.enabled?"enabled":"disabled", event.id, event.condition_id, event.period, event.action, event.argument, event.strict, t.c_str()); - - ret = true; - } - mysql_free_result(result); - } else { - LogFile->write(EQEMuLog::Error, "Error in LoadDBEvent query '%s': %s", query, errbuf); - safe_delete_array(query); + std::string query = StringFormat("SELECT id, cond_id, period, " + "next_minute, next_hour, next_day, " + "next_month, next_year, enabled, " + "action, argument, strict, zone " + "FROM spawn_events WHERE id = %d", event_id); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in LoadDBEvent query '%s': %s", query.c_str(), results.ErrorMessage().c_str()); + return false; } - return(ret); + + if (results.RowCount() == 0) + return false; + + auto row = results.begin(); + + event.id = atoi(row[0]); + event.condition_id = atoi(row[1]); + event.period = atoi(row[2]); + + event.next.minute = atoi(row[3]); + event.next.hour = atoi(row[4]); + event.next.day = atoi(row[5]); + event.next.month = atoi(row[6]); + event.next.year = atoi(row[7]); + + event.enabled = atoi(row[8]) != 0; + event.action = (SpawnEvent::Action) atoi(row[9]); + event.argument = atoi(row[10]); + event.strict = atoi(row[11]) != 0; + zone_name = row[12]; + + std::string timeAsString; + EQTime::ToString(&event.next, timeAsString); + + _log(SPAWNS__CONDITIONS, "(LoadDBEvent) Loaded %s spawn event %d on condition %d with period %d, action %d, argument %d, strict %d. Will trigger at %s", event.enabled? "enabled": "disabled", event.id, event.condition_id, event.period, event.action, event.argument, event.strict, timeAsString.c_str()); + + return true; } bool SpawnConditionManager::LoadSpawnConditions(const char* zone_name, uint32 instance_id) { - char errbuf[MYSQL_ERRMSG_SIZE]; - char* query = 0; - MYSQL_RES *result; - MYSQL_ROW row; - int len; - //clear out old stuff.. spawn_conditions.clear(); - //load spawn conditions - SpawnCondition cond; - len = MakeAnyLenString(&query, "SELECT id, onchange, value FROM spawn_conditions WHERE zone='%s'", zone_name); - if (database.RunQuery(query, len, errbuf, &result)) { - safe_delete_array(query); - while((row = mysql_fetch_row(result))) { - cond.condition_id = atoi(row[0]); - cond.value = atoi(row[2]); - cond.on_change = (SpawnCondition::OnChange) atoi(row[1]); - spawn_conditions[cond.condition_id] = cond; - _log(SPAWNS__CONDITIONS, "Loaded spawn condition %d with value %d and on_change %d", cond.condition_id, cond.value, cond.on_change); - } - mysql_free_result(result); - } else { - LogFile->write(EQEMuLog::Error, "Error in LoadSpawnConditions query '%s': %s", query, errbuf); - safe_delete_array(query); + + std::string query = StringFormat("SELECT id, onchange, value " + "FROM spawn_conditions " + "WHERE zone = '%s'", zone_name); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in LoadSpawnConditions query '%s': %s", query.c_str(), results.ErrorMessage().c_str()); return false; - } + } + + for (auto row = results.begin(); row != results.end(); ++row) { + //load spawn conditions + SpawnCondition cond; + + cond.condition_id = atoi(row[0]); + cond.value = atoi(row[2]); + cond.on_change = (SpawnCondition::OnChange) atoi(row[1]); + spawn_conditions[cond.condition_id] = cond; + + _log(SPAWNS__CONDITIONS, "Loaded spawn condition %d with value %d and on_change %d", cond.condition_id, cond.value, cond.on_change); + } //load values - len = MakeAnyLenString(&query, "SELECT id, value FROM spawn_condition_values WHERE zone='%s' and instance_id=%u", zone_name, instance_id); - if (database.RunQuery(query, len, errbuf, &result)) { - safe_delete_array(query); - while((row = mysql_fetch_row(result))) - { - std::map::iterator iter = spawn_conditions.find(atoi(row[0])); - if(iter != spawn_conditions.end()) - { - iter->second.value = atoi(row[1]); - } - } - mysql_free_result(result); - } - else - { - LogFile->write(EQEMuLog::Error, "Error in LoadSpawnConditions query '%s': %s", query, errbuf); - safe_delete_array(query); + query = StringFormat("SELECT id, value FROM spawn_condition_values " + "WHERE zone = '%s' AND instance_id = %u", + zone_name, instance_id); + results = database.QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in LoadSpawnConditions query '%s': %s", query.c_str(), results.ErrorMessage().c_str()); spawn_conditions.clear(); return false; - } + } + + for (auto row = results.begin(); row != results.end(); ++row) { + auto iter = spawn_conditions.find(atoi(row[0])); + + if(iter != spawn_conditions.end()) + iter->second.value = atoi(row[1]); + } //load spawn events - SpawnEvent event; - len = MakeAnyLenString(&query, - "SELECT id,cond_id,period,next_minute,next_hour,next_day,next_month,next_year,enabled,action,argument,strict " - "FROM spawn_events WHERE zone='%s'", zone_name); - if (database.RunQuery(query, len, errbuf, &result)) { - safe_delete_array(query); - while((row = mysql_fetch_row(result))) { - event.id = atoi(row[0]); - event.condition_id = atoi(row[1]); - event.period = atoi(row[2]); - if(event.period == 0) { - LogFile->write(EQEMuLog::Error, "Refusing to load spawn event #%d because it has a period of 0\n", event.id); - continue; - } - - event.next.minute = atoi(row[3]); - event.next.hour = atoi(row[4]); - event.next.day = atoi(row[5]); - event.next.month = atoi(row[6]); - event.next.year = atoi(row[7]); - - event.enabled = atoi(row[8])==0?false:true; - event.action = (SpawnEvent::Action) atoi(row[9]); - event.argument = atoi(row[10]); - event.strict = atoi(row[11])==0?false:true; - spawn_events.push_back(event); - - _log(SPAWNS__CONDITIONS, "(LoadSpawnConditions) Loaded %s spawn event %d on condition %d with period %d, action %d, argument %d, strict %d", - event.enabled?"enabled":"disabled", event.id, event.condition_id, event.period, event.action, event.argument, event.strict); - } - mysql_free_result(result); - } else { - LogFile->write(EQEMuLog::Error, "Error in LoadSpawnConditions events query '%s': %s", query, errbuf); - safe_delete_array(query); + query = StringFormat("SELECT id, cond_id, period, next_minute, next_hour, " + "next_day, next_month, next_year, enabled, action, argument, strict " + "FROM spawn_events WHERE zone = '%s'", zone_name); + results = database.QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in LoadSpawnConditions events query '%s': %s", query.c_str(), results.ErrorMessage().c_str()); return false; - } + } + + for (auto row = results.begin(); row != results.end(); ++row) { + SpawnEvent event; + + event.id = atoi(row[0]); + event.condition_id = atoi(row[1]); + event.period = atoi(row[2]); + + if(event.period == 0) { + LogFile->write(EQEMuLog::Error, "Refusing to load spawn event #%d because it has a period of 0\n", event.id); + continue; + } + + event.next.minute = atoi(row[3]); + event.next.hour = atoi(row[4]); + event.next.day = atoi(row[5]); + event.next.month = atoi(row[6]); + event.next.year = atoi(row[7]); + + event.enabled = atoi(row[8])==0?false:true; + event.action = (SpawnEvent::Action) atoi(row[9]); + event.argument = atoi(row[10]); + event.strict = atoi(row[11])==0?false:true; + + spawn_events.push_back(event); + + _log(SPAWNS__CONDITIONS, "(LoadSpawnConditions) Loaded %s spawn event %d on condition %d with period %d, action %d, argument %d, strict %d", event.enabled? "enabled": "disabled", event.id, event.condition_id, event.period, event.action, event.argument, event.strict); + } //now we need to catch up on events that happened while we were away //and use them to alter just the condition variables. @@ -855,18 +827,14 @@ bool SpawnConditionManager::LoadSpawnConditions(const char* zone_name, uint32 in TimeOfDay_Struct tod; zone->zone_time.getEQTimeOfDay(&tod); - std::vector::iterator cur,end; - cur = spawn_events.begin(); - end = spawn_events.end(); - bool ran; - for(; cur != end; ++cur) { + for(auto cur = spawn_events.begin(); cur != spawn_events.end(); ++cur) { SpawnEvent &cevent = *cur; bool StrictCheck = false; - if(cevent.strict && - cevent.next.hour == tod.hour && - cevent.next.day == tod.day && - cevent.next.month == tod.month && + if(cevent.strict && + cevent.next.hour == tod.hour && + cevent.next.day == tod.day && + cevent.next.month == tod.month && cevent.next.year == tod.year) StrictCheck = true; @@ -874,43 +842,42 @@ bool SpawnConditionManager::LoadSpawnConditions(const char* zone_name, uint32 in if(!cevent.enabled || !StrictCheck) SetCondition(zone->GetShortName(), zone->GetInstanceID(),cevent.condition_id,0); - if(cevent.enabled) - { - //watch for special case of all 0s, which means to reset next to now - if(cevent.next.year == 0 && cevent.next.month == 0 && cevent.next.day == 0 && cevent.next.hour == 0 && cevent.next.minute == 0) { - _log(SPAWNS__CONDITIONS, "Initial next trigger time set for spawn event %d", cevent.id); - memcpy(&cevent.next, &tod, sizeof(cevent.next)); - //add one period - EQTime::AddMinutes(cevent.period, &cevent.next); - //save it in the db. - UpdateDBEvent(cevent); - continue; //were done with this event. - } + if(!cevent.enabled) + continue; - ran = false; - while(EQTime::IsTimeBefore(&tod, &cevent.next)) { - _log(SPAWNS__CONDITIONS, "Catch up triggering on event %d", cevent.id); - //this event has been triggered. - //execute the event - if(!cevent.strict || StrictCheck) - ExecEvent(cevent, false); - - //add the period of the event to the trigger time - EQTime::AddMinutes(cevent.period, &cevent.next); - ran = true; - } - //only write it out if the event actually ran - if(ran) { - //save the event in the DB - UpdateDBEvent(cevent); - } - } + //watch for special case of all 0s, which means to reset next to now + if(cevent.next.year == 0 && cevent.next.month == 0 && cevent.next.day == 0 && cevent.next.hour == 0 && cevent.next.minute == 0) { + _log(SPAWNS__CONDITIONS, "Initial next trigger time set for spawn event %d", cevent.id); + memcpy(&cevent.next, &tod, sizeof(cevent.next)); + //add one period + EQTime::AddMinutes(cevent.period, &cevent.next); + //save it in the db. + UpdateDBEvent(cevent); + continue; //were done with this event. + } + + bool ran = false; + while(EQTime::IsTimeBefore(&tod, &cevent.next)) { + _log(SPAWNS__CONDITIONS, "Catch up triggering on event %d", cevent.id); + //this event has been triggered. + //execute the event + if(!cevent.strict || StrictCheck) + ExecEvent(cevent, false); + + //add the period of the event to the trigger time + EQTime::AddMinutes(cevent.period, &cevent.next); + ran = true; + } + + //only write it out if the event actually ran + if(ran) + UpdateDBEvent(cevent); //save the event in the DB } //now our event timers are all up to date, find our closest event. FindNearestEvent(); - return(true); + return true; } void SpawnConditionManager::FindNearestEvent() { @@ -926,7 +893,7 @@ void SpawnConditionManager::FindNearestEvent() { if(cevent.enabled) { //see if this event is before our last nearest - if(EQTime::IsTimeBefore(&next_event, &cevent.next)) + if(EQTime::IsTimeBefore(&next_event, &cevent.next)) { memcpy(&next_event, &cevent.next, sizeof(next_event)); next_id = cevent.id; @@ -1162,37 +1129,28 @@ int16 SpawnConditionManager::GetCondition(const char *zone_short, uint32 instanc } SpawnCondition &cond = condi->second; - return(cond.value); - } else { - //this is a remote spawn condition, grab it from the DB - char errbuf[MYSQL_ERRMSG_SIZE]; - char* query = 0; - MYSQL_RES *result; - MYSQL_ROW row; - int len; - - int16 value; - - //load spawn conditions - SpawnCondition cond; - len = MakeAnyLenString(&query, "SELECT value FROM spawn_condition_values WHERE zone='%s' AND instance_id=%u AND id=%d", - zone_short, instance_id, condition_id); - if (database.RunQuery(query, len, errbuf, &result)) { - safe_delete_array(query); - if((row = mysql_fetch_row(result))) { - value = atoi(row[0]); - } else { - _log(SPAWNS__CONDITIONS, "Unable to load remote condition %d from zone %s in Get request.", condition_id, zone_short); - value = 0; //dunno a better thing to do... - } - mysql_free_result(result); - } else { - _log(SPAWNS__CONDITIONS, "Unable to query remote condition %d from zone %s in Get request.", condition_id, zone_short); - safe_delete_array(query); - value = 0; //dunno a better thing to do... - } - return(value); + return cond.value; } + + //this is a remote spawn condition, grab it from the DB + //load spawn conditions + std::string query = StringFormat("SELECT value FROM spawn_condition_values " + "WHERE zone = '%s' AND instance_id = %u AND id = %d", + zone_short, instance_id, condition_id); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + _log(SPAWNS__CONDITIONS, "Unable to query remote condition %d from zone %s in Get request.", condition_id, zone_short); + return 0; //dunno a better thing to do... + } + + if (results.RowCount() == 0) { + _log(SPAWNS__CONDITIONS, "Unable to load remote condition %d from zone %s in Get request.", condition_id, zone_short); + return 0; //dunno a better thing to do... + } + + auto row = results.begin(); + + return atoi(row[0]); } bool SpawnConditionManager::Check(uint16 condition, int16 min_value) { diff --git a/zone/spawngroup.cpp b/zone/spawngroup.cpp index 04e37cd59..81d6c5d51 100644 --- a/zone/spawngroup.cpp +++ b/zone/spawngroup.cpp @@ -28,8 +28,7 @@ extern EntityList entity_list; -SpawnEntry::SpawnEntry( uint32 in_NPCType, int in_chance, uint8 in_npc_spawn_limit ) -{ +SpawnEntry::SpawnEntry( uint32 in_NPCType, int in_chance, uint8 in_npc_spawn_limit ) { NPCType = in_NPCType; chance = in_chance; npc_spawn_limit = in_npc_spawn_limit; @@ -57,7 +56,6 @@ uint32 SpawnGroup::GetNPCType() { int npcType = 0; int totalchance = 0; - //check limits on this spawn group and npc type if(!entity_list.LimitCheckGroup(id, group_spawn_limit)) return(0); @@ -68,7 +66,6 @@ uint32 SpawnGroup::GetNPCType() { for(; cur != end; ++cur) { SpawnEntry *se = *cur; - //check limits on this spawn group and npc type if(!entity_list.LimitCheckType(se->NPCType, se->npc_spawn_limit)) continue; @@ -93,7 +90,6 @@ uint32 SpawnGroup::GetNPCType() { roll -= se->chance; } } - //CODER implement random table return npcType; } @@ -144,106 +140,92 @@ bool SpawnGroupList::RemoveSpawnGroup(uint32 in_id) { } bool ZoneDatabase::LoadSpawnGroups(const char* zone_name, uint16 version, SpawnGroupList* spawn_group_list) { - char errbuf[MYSQL_ERRMSG_SIZE]; - char *query = 0; - MYSQL_RES *result; - MYSQL_ROW row; - // CODER new spawn code - query = 0; - if (RunQuery(query, MakeAnyLenString(&query, "SELECT DISTINCT(spawngroupID), spawngroup.name, spawngroup.spawn_limit, spawngroup.dist, spawngroup.max_x, spawngroup.min_x, spawngroup.max_y, spawngroup.min_y, spawngroup.delay, spawngroup.despawn, spawngroup.despawn_timer, spawngroup.mindelay FROM spawn2,spawngroup WHERE spawn2.spawngroupID=spawngroup.ID and spawn2.version=%u and zone='%s'", version, zone_name), errbuf, &result)) - { - safe_delete_array(query); - while((row = mysql_fetch_row(result))) { - SpawnGroup* newSpawnGroup = new SpawnGroup( atoi(row[0]), row[1], atoi(row[2]), atof(row[3]), atof(row[4]), atof(row[5]), atof(row[6]), atof(row[7]), atoi(row[8]), atoi(row[9]), atoi(row[10]), atoi(row[11])); - spawn_group_list->AddSpawnGroup(newSpawnGroup); - } - mysql_free_result(result); - } - else - { - std::cerr << "Error2 in PopulateZoneLists query '" << query << "' " << errbuf << std::endl; - safe_delete_array(query); + std::string query = StringFormat("SELECT DISTINCT(spawngroupID), spawngroup.name, spawngroup.spawn_limit, " + "spawngroup.dist, spawngroup.max_x, spawngroup.min_x, " + "spawngroup.max_y, spawngroup.min_y, spawngroup.delay, " + "spawngroup.despawn, spawngroup.despawn_timer, spawngroup.mindelay " + "FROM spawn2, spawngroup WHERE spawn2.spawngroupID = spawngroup.ID " + "AND spawn2.version = %u and zone = '%s'", version, zone_name); + auto results = QueryDatabase(query); + if (!results.Success()) { + _log(ZONE__SPAWNS, "Error2 in PopulateZoneLists query '%s' ", query.c_str()); return false; - } + } - query = 0; - if (RunQuery(query, MakeAnyLenString(&query, - "SELECT DISTINCT spawnentry.spawngroupID, npcid, chance, " - "npc_types.spawn_limit AS sl " - "FROM spawnentry, spawn2, npc_types " - "WHERE spawnentry.npcID=npc_types.id AND spawnentry.spawngroupID=spawn2.spawngroupID " - "AND zone='%s'", zone_name), errbuf, &result)) { - safe_delete_array(query); - while((row = mysql_fetch_row(result))) - { - SpawnEntry* newSpawnEntry = new SpawnEntry( atoi(row[1]), atoi(row[2]), row[3]?atoi(row[3]):0); - SpawnGroup *sg = spawn_group_list->GetSpawnGroup(atoi(row[0])); - if (sg) - sg->AddSpawnEntry(newSpawnEntry); - else - std::cout << "Error in SpawngroupID: " << row[0] << std::endl; - } - mysql_free_result(result); - } - else - { - std::cerr << "Error3 in PopulateZoneLists query '" << query << "' " << errbuf << std::endl; - safe_delete_array(query); + for (auto row = results.begin(); row != results.end(); ++row) { + SpawnGroup* newSpawnGroup = new SpawnGroup(atoi(row[0]), row[1], atoi(row[2]), atof(row[3]), + atof(row[4]), atof(row[5]), atof(row[6]), atof(row[7]), + atoi(row[8]), atoi(row[9]), atoi(row[10]), atoi(row[11])); + spawn_group_list->AddSpawnGroup(newSpawnGroup); + } + + query = StringFormat("SELECT DISTINCT spawnentry.spawngroupID, npcid, chance, " + "npc_types.spawn_limit AS sl " + "FROM spawnentry, spawn2, npc_types " + "WHERE spawnentry.npcID=npc_types.id " + "AND spawnentry.spawngroupID = spawn2.spawngroupID " + "AND zone = '%s'", zone_name); + results = QueryDatabase(query); + if (!results.Success()) { + _log(ZONE__SPAWNS, "Error2 in PopulateZoneLists query '%'", query.c_str()); return false; - } + } + + for (auto row = results.begin(); row != results.end(); ++row) { + SpawnEntry* newSpawnEntry = new SpawnEntry( atoi(row[1]), atoi(row[2]), row[3]?atoi(row[3]):0); + SpawnGroup *sg = spawn_group_list->GetSpawnGroup(atoi(row[0])); + + if (!sg) { + _log(ZONE__SPAWNS, "Error in LoadSpawnGroups %s ", query.c_str()); + continue; + } + + sg->AddSpawnEntry(newSpawnEntry); + } - // CODER end new spawn code return true; } bool ZoneDatabase::LoadSpawnGroupsByID(int spawngroupid, SpawnGroupList* spawn_group_list) { - char errbuf[MYSQL_ERRMSG_SIZE]; - char *query = 0; - MYSQL_RES *result; - MYSQL_ROW row; - // CODER new spawn code - query = 0; - if (RunQuery(query, MakeAnyLenString(&query, "SELECT DISTINCT spawngroup.id, spawngroup.name, spawngroup.spawn_limit, spawngroup.dist, spawngroup.max_x, spawngroup.min_x, spawngroup.max_y, spawngroup.min_y, spawngroup.delay, spawngroup.despawn, spawngroup.despawn_timer, spawngroup.mindelay FROM spawngroup WHERE spawngroup.ID='%i'", spawngroupid), errbuf, &result)) - { - safe_delete_array(query); - while((row = mysql_fetch_row(result))) { - SpawnGroup* newSpawnGroup = new SpawnGroup( atoi(row[0]), row[1], atoi(row[2]), atof(row[3]), atof(row[4]), atof(row[5]), atof(row[6]), atof(row[7]), atoi(row[8]), atoi(row[9]), atoi(row[10]), atoi(row[11])); - spawn_group_list->AddSpawnGroup(newSpawnGroup); - } - mysql_free_result(result); - } - else - { - std::cerr << "Error2 in PopulateZoneLists query '" << query << "' " << errbuf << std::endl; - safe_delete_array(query); + std::string query = StringFormat("SELECT DISTINCT(spawngroup.id), spawngroup.name, spawngroup.spawn_limit, " + "spawngroup.dist, spawngroup.max_x, spawngroup.min_x, " + "spawngroup.max_y, spawngroup.min_y, spawngroup.delay, " + "spawngroup.despawn, spawngroup.despawn_timer, spawngroup.mindelay " + "FROM spawngroup WHERE spawngroup.ID = '%i'", spawngroupid); + auto results = QueryDatabase(query); + if (!results.Success()) { + _log(ZONE__SPAWNS, "Error2 in PopulateZoneLists query %s", query.c_str()); + return false; + } + + for (auto row = results.begin(); row != results.end(); ++row) { + SpawnGroup* newSpawnGroup = new SpawnGroup(atoi(row[0]), row[1], atoi(row[2]), atof(row[3]), atof(row[4]), atof(row[5]), atof(row[6]), atof(row[7]), atoi(row[8]), atoi(row[9]), atoi(row[10]), atoi(row[11])); + spawn_group_list->AddSpawnGroup(newSpawnGroup); + } + + query = StringFormat("SELECT DISTINCT(spawnentry.spawngroupID), spawnentry.npcid, " + "spawnentry.chance, spawngroup.spawn_limit FROM spawnentry, spawngroup " + "WHERE spawnentry.spawngroupID = '%i' AND spawngroup.spawn_limit = '0' " + "ORDER BY chance", spawngroupid); + results = QueryDatabase(query); + if (!results.Success()) { + _log(ZONE__SPAWNS, "Error3 in PopulateZoneLists query '%s'", query.c_str()); return false; } - query = 0; - if (RunQuery(query, MakeAnyLenString(&query, - "SELECT DISTINCT spawnentry.spawngroupID, spawnentry.npcid, spawnentry.chance, spawngroup.spawn_limit FROM spawnentry,spawngroup WHERE spawnentry.spawngroupID='%i' AND spawngroup.spawn_limit='0' ORDER by chance", spawngroupid), errbuf, &result)) { - safe_delete_array(query); - while((row = mysql_fetch_row(result))) - { - SpawnEntry* newSpawnEntry = new SpawnEntry( atoi(row[1]), atoi(row[2]), row[3]?atoi(row[3]):0); - SpawnGroup *sg = spawn_group_list->GetSpawnGroup(atoi(row[0])); - if (sg) - sg->AddSpawnEntry(newSpawnEntry); - else - std::cout << "Error in SpawngroupID: " << row[0] << std::endl; - } - mysql_free_result(result); - } - else - { - std::cerr << "Error3 in PopulateZoneLists query '" << query << "' " << errbuf << std::endl; - safe_delete_array(query); - return false; - } + for(auto row = results.begin(); row != results.end(); ++row) { + SpawnEntry* newSpawnEntry = new SpawnEntry( atoi(row[1]), atoi(row[2]), row[3]?atoi(row[3]):0); + SpawnGroup *sg = spawn_group_list->GetSpawnGroup(atoi(row[0])); + if (!sg) { + _log(ZONE__SPAWNS, "Error in SpawngroupID: %s ", row[0]); + continue; + } + + sg->AddSpawnEntry(newSpawnEntry); + } - // CODER end new spawn code return true; } diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index abc94f8f5..0431eb9b1 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -28,8 +28,6 @@ #include "../common/rulesys.h" - - int Mob::GetKickDamage() { int multiple=(GetLevel()*100/5); multiple += 100; @@ -64,11 +62,9 @@ int Mob::GetBashDamage() { } void Mob::ApplySpecialAttackMod(SkillUseTypes skill, int32 &dmg, int32 &mindmg) { - int item_slot = -1; //1: Apply bonus from AC (BOOT/SHIELD/HANDS) est. 40AC=6dmg if (IsClient()){ - switch (skill){ case SkillFlyingKick: @@ -112,10 +108,8 @@ void Mob::DoSpecialAttackDamage(Mob *who, SkillUseTypes skill, int32 max_damage, if(hate_override > -1) hate = hate_override; - if(skill == SkillBash) - { - if(IsClient()) - { + if(skill == SkillBash){ + if(IsClient()){ ItemInst *item = CastToClient()->GetInv().GetItem(MainSecondary); if(item) { @@ -186,6 +180,10 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { CombatAbility_Struct* ca_atk = (CombatAbility_Struct*) app->pBuffer; + /* Check to see if actually have skill */ + if (!MaxSkill(static_cast(ca_atk->m_skill))) + return; + if(GetTarget()->GetID() != ca_atk->m_target) return; //invalid packet. @@ -274,8 +272,7 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { return; } - if ((ca_atk->m_atk == 100) && (ca_atk->m_skill == SkillFrenzy)) - { + if ((ca_atk->m_atk == 100) && (ca_atk->m_skill == SkillFrenzy)){ CheckIncreaseSkill(SkillFrenzy, GetTarget(), 10); int AtkRounds = 3; int skillmod = 100*GetSkill(SkillFrenzy)/MaxSkill(SkillFrenzy); @@ -311,8 +308,7 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { return; } - switch(GetClass()) - { + switch(GetClass()){ case BERSERKER: case WARRIOR: case RANGER: @@ -384,8 +380,7 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { } ReuseTime = (ReuseTime*HasteMod)/100; - if(ReuseTime > 0) - { + if(ReuseTime > 0){ p_timers.Start(pTimerCombatAbility, ReuseTime); } } @@ -403,8 +398,7 @@ int Mob::MonkSpecialAttack(Mob* other, uint8 unchecked_type) SkillUseTypes skill_type; //to avoid casting... even though it "would work" uint8 itemslot = MainFeet; - switch(unchecked_type) - { + switch(unchecked_type){ case SkillFlyingKick:{ skill_type = SkillFlyingKick; max_dmg = ((GetSTR()+GetSkill(skill_type)) * RuleI(Combat, FlyingKickBonus) / 100) + 35; @@ -484,8 +478,7 @@ int Mob::MonkSpecialAttack(Mob* other, uint8 unchecked_type) else ht = ndamage = MakeRandomInt(min_dmg, max_dmg); } - else - { + else{ ht = max_dmg; } } @@ -535,7 +528,6 @@ void Mob::TryBackstab(Mob *other, int ReuseTime) { RogueBackstab(other,false,ReuseTime); if (level > 54) { - if(IsClient() && CastToClient()->CheckDoubleAttack(false)) { if(other->GetHP() > 0) @@ -619,12 +611,10 @@ void Mob::RogueBackstab(Mob* other, bool min_damage, int ReuseTime) } // determine minimum hits - if (level < 51) - { + if (level < 51) { min_hit = (level*15/10); } - else - { + else { // Trumpcard: Replaced switch statement with formula calc. This will give minhit increases all the way to 65. min_hit = (level * ( level*5 - 105)) / 100; } @@ -636,8 +626,7 @@ void Mob::RogueBackstab(Mob* other, bool min_damage, int ReuseTime) if(min_damage){ ndamage = min_hit; } - else - { + else { if (max_hit < min_hit) max_hit = min_hit; @@ -645,7 +634,6 @@ void Mob::RogueBackstab(Mob* other, bool min_damage, int ReuseTime) ndamage = max_hit; else ndamage = MakeRandomInt(min_hit, max_hit); - } } } @@ -792,28 +780,24 @@ void Client::RangedAttack(Mob* other, bool CanDoubleAttack) { return; } - SendItemAnimation(GetTarget(), AmmoItem, SkillArchery); - + SendItemAnimation(GetTarget(), AmmoItem, SkillArchery); DoArcheryAttackDmg(GetTarget(), RangeWeapon, Ammo); //EndlessQuiver AA base1 = 100% Chance to avoid consumption arrow. int ChanceAvoidConsume = aabonuses.ConsumeProjectile + itembonuses.ConsumeProjectile + spellbonuses.ConsumeProjectile; - if (!ChanceAvoidConsume || (ChanceAvoidConsume < 100 && MakeRandomInt(0,99) > ChanceAvoidConsume)){ - + if (!ChanceAvoidConsume || (ChanceAvoidConsume < 100 && MakeRandomInt(0,99) > ChanceAvoidConsume)){ DeleteItemInInventory(ammo_slot, 1, true); mlog(COMBAT__RANGED, "Consumed one arrow from slot %d", ammo_slot); } else { mlog(COMBAT__RANGED, "Endless Quiver prevented ammo consumption."); } - CheckIncreaseSkill(SkillArchery, GetTarget(), -15); - + CheckIncreaseSkill(SkillArchery, GetTarget(), -15); CommonBreakInvisible(); } -void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const ItemInst* Ammo, uint16 weapon_damage, int16 chance_mod, int16 focus, int ReuseTime) -{ +void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const ItemInst* Ammo, uint16 weapon_damage, int16 chance_mod, int16 focus, int ReuseTime) { if (!CanDoSpecialAttack(other)) return; @@ -842,8 +826,7 @@ void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Item if (focus) //From FcBaseEffects WDmg += WDmg*focus/100; - if((WDmg > 0) || (ADmg > 0)) - { + if((WDmg > 0) || (ADmg > 0)) { if(WDmg < 0) WDmg = 0; if(ADmg < 0) @@ -944,8 +927,7 @@ void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Item } //try proc on hits and misses - if((RangeWeapon != nullptr) && GetTarget() && other && !other->HasDied()) - { + if((RangeWeapon != nullptr) && GetTarget() && other && !other->HasDied()){ TryWeaponProc(RangeWeapon, other, MainRange); } @@ -965,14 +947,19 @@ void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Item void NPC::RangedAttack(Mob* other) { + + if (!other) + return; //make sure the attack and ranged timers are up //if the ranged timer is disabled, then they have no ranged weapon and shouldent be attacking anyhow - if((attack_timer.Enabled() && !attack_timer.Check(false)) || (ranged_timer.Enabled() && !ranged_timer.Check())) - { + if((attack_timer.Enabled() && !attack_timer.Check(false)) || (ranged_timer.Enabled() && !ranged_timer.Check())){ mlog(COMBAT__RANGED, "Archery canceled. Timer not up. Attack %d, ranged %d", attack_timer.GetRemainingTime(), ranged_timer.GetRemainingTime()); return; } + if(!CheckLosFN(other)) + return; + int attacks = GetSpecialAbilityParam(SPECATK_RANGED_ATK, 0); attacks = attacks > 0 ? attacks : 1; for(int i = 0; i < attacks; ++i) { @@ -1087,9 +1074,7 @@ void NPC::RangedAttack(Mob* other) } } -uint16 Mob::GetThrownDamage(int16 wDmg, int32& TotalDmg, int& minDmg) -{ - +uint16 Mob::GetThrownDamage(int16 wDmg, int32& TotalDmg, int& minDmg) { uint16 MaxDmg = (((2 * wDmg) * GetDamageTable(SkillThrowing)) / 100); if (MaxDmg == 0) @@ -1101,8 +1086,7 @@ uint16 Mob::GetThrownDamage(int16 wDmg, int32& TotalDmg, int& minDmg) TotalDmg = MakeRandomInt(1, MaxDmg); minDmg = 1; - if(GetLevel() > 25) - { + if(GetLevel() > 25){ TotalDmg += ((GetLevel()-25)/3); minDmg += ((GetLevel()-25)/3); minDmg += minDmg * GetMeleeMinDamageMod_SE(SkillThrowing) / 100; @@ -1195,9 +1179,8 @@ void Client::ThrowingAttack(Mob* other, bool CanDoubleAttack) { //old was 51 //consume ammo DeleteItemInInventory(ammo_slot, 1, true); - CheckIncreaseSkill(SkillThrowing, GetTarget()); - - CommonBreakInvisible(); + CheckIncreaseSkill(SkillThrowing, GetTarget()); + CommonBreakInvisible(); } void Mob::DoThrowingAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Item_Struct* item, uint16 weapon_damage, int16 chance_mod,int16 focus, int ReuseTime) @@ -1227,8 +1210,7 @@ void Mob::DoThrowingAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Ite if (GetClass() == ROGUE && (BehindMob(other, GetX(), GetY()))) Assassinate_Dmg = TryAssassinate(other, SkillThrowing, ranged_timer.GetDuration()); - if(WDmg > 0) - { + if(WDmg > 0){ int minDmg = 1; uint16 MaxDmg = GetThrownDamage(WDmg, TotalDmg, minDmg); @@ -1318,8 +1300,7 @@ void Mob::SendItemAnimation(Mob *to, const Item_Struct *item, SkillUseTypes skil safe_delete(outapp); } -void Mob::ProjectileAnimation(Mob* to, int item_id, bool IsArrow, float speed, float angle, float tilt, float arc, const char *IDFile, SkillUseTypes skillInUse) { - +void Mob::ProjectileAnimation(Mob* to, int item_id, bool IsArrow, float speed, float angle, float tilt, float arc, const char *IDFile, SkillUseTypes skillInUse) { if (!to) return; @@ -1453,17 +1434,11 @@ void NPC::DoClassAttacks(Mob *target) { break; case MONK: case MONKGM: { uint8 satype = SkillKick; - if(level > 29) { - satype = SkillFlyingKick; - } else if(level > 24) { - satype = SkillDragonPunch; - } else if(level > 19) { - satype = SkillEagleStrike; - } else if(level > 9) { - satype = SkillTigerClaw; - } else if(level > 4) { - satype = SkillRoundKick; - } + if(level > 29) { satype = SkillFlyingKick; } + else if(level > 24) { satype = SkillDragonPunch; } + else if(level > 19) { satype = SkillEagleStrike; } + else if(level > 9) { satype = SkillTigerClaw; } + else if(level > 4) { satype = SkillRoundKick; } reuse = MonkSpecialAttack(target, satype); reuse *= 1000; @@ -1472,8 +1447,7 @@ void NPC::DoClassAttacks(Mob *target) { } case WARRIOR: case WARRIORGM:{ if(level >= RuleI(Combat, NPCBashKickLevel)){ - if(MakeRandomInt(0, 100) > 25) //tested on live, warrior mobs both kick and bash, kick about 75% of the time, casting doesn't seem to make a difference. - { + if(MakeRandomInt(0, 100) > 25){ //tested on live, warrior mobs both kick and bash, kick about 75% of the time, casting doesn't seem to make a difference. DoAnim(animKick); int32 dmg = 0; @@ -1494,8 +1468,7 @@ void NPC::DoClassAttacks(Mob *target) { DoSpecialAttackDamage(target, SkillKick, dmg, 1, -1, reuse); did_attack = true; } - else - { + else { DoAnim(animTailRake); int32 dmg = 0; @@ -1518,8 +1491,7 @@ void NPC::DoClassAttacks(Mob *target) { } break; } - case BERSERKER: case BERSERKERGM: - { + case BERSERKER: case BERSERKERGM:{ int AtkRounds = 3; int32 max_dmg = 26 + ((GetLevel()-6) * 2); int32 min_dmg = 0; @@ -1535,8 +1507,7 @@ void NPC::DoClassAttacks(Mob *target) { reuse = FrenzyReuseTime * 1000; - while(AtkRounds > 0) { - + while(AtkRounds > 0) { if (GetTarget() && (AtkRounds == 1 || MakeRandomInt(0,100) < 75)){ DoSpecialAttackDamage(GetTarget(), SkillFrenzy, max_dmg, min_dmg, -1 , reuse, true); } @@ -1574,8 +1545,7 @@ void NPC::DoClassAttacks(Mob *target) { } case CLERIC: case CLERICGM: //clerics can bash too. case SHADOWKNIGHT: case SHADOWKNIGHTGM: - case PALADIN: case PALADINGM: - { + case PALADIN: case PALADINGM:{ if(level >= RuleI(Combat, NPCBashKickLevel)){ DoAnim(animTailRake); int32 dmg = 0; @@ -1615,8 +1585,7 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) return; //check range for all these abilities, they are all close combat stuff - if(!CombatRange(ca_target)) - { + if(!CombatRange(ca_target)){ return; } @@ -1638,10 +1607,8 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) uint16 skill_to_use = -1; - if (skill == -1){ - - switch(GetClass()) - { + if (skill == -1){ + switch(GetClass()){ case WARRIOR: case RANGER: case BEASTLORD: @@ -1692,14 +1659,11 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) if(skill_to_use == -1) return; - if(skill_to_use == SkillBash) - { - if (ca_target!=this) - { + if(skill_to_use == SkillBash) { + if (ca_target!=this) { DoAnim(animTailRake); - if(GetWeaponDamage(ca_target, GetInv().GetItem(MainSecondary)) <= 0 && - GetWeaponDamage(ca_target, GetInv().GetItem(MainShoulders)) <= 0){ + if(GetWeaponDamage(ca_target, GetInv().GetItem(MainSecondary)) <= 0 && GetWeaponDamage(ca_target, GetInv().GetItem(MainShoulders)) <= 0){ dmg = -5; } else{ @@ -1720,16 +1684,14 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) DoSpecialAttackDamage(ca_target, SkillBash, dmg, 1,-1,ReuseTime); - if(ReuseTime > 0 && !IsRiposte) - { + if(ReuseTime > 0 && !IsRiposte) { p_timers.Start(pTimerCombatAbility, ReuseTime); } } return; } - if(skill_to_use == SkillFrenzy) - { + if(skill_to_use == SkillFrenzy){ CheckIncreaseSkill(SkillFrenzy, GetTarget(), 10); int AtkRounds = 3; int skillmod = 100*GetSkill(SkillFrenzy)/MaxSkill(SkillFrenzy); @@ -1763,10 +1725,8 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) return; } - if(skill_to_use == SkillKick) - { - if(ca_target!=this) - { + if(skill_to_use == SkillKick){ + if(ca_target!=this){ DoAnim(animKick); if(GetWeaponDamage(ca_target, GetInv().GetItem(MainFeet)) <= 0){ @@ -1790,12 +1750,7 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) } } - if(skill_to_use == SkillFlyingKick || - skill_to_use == SkillDragonPunch || - skill_to_use == SkillEagleStrike || - skill_to_use == SkillTigerClaw || - skill_to_use == SkillRoundKick) - { + if(skill_to_use == SkillFlyingKick || skill_to_use == SkillDragonPunch || skill_to_use == SkillEagleStrike || skill_to_use == SkillTigerClaw || skill_to_use == SkillRoundKick) { ReuseTime = MonkSpecialAttack(ca_target, skill_to_use) - 1; MonkSpecialAttack(ca_target, skill_to_use); @@ -1809,8 +1764,7 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) int MonkSPA [5] = { SkillFlyingKick, SkillDragonPunch, SkillEagleStrike, SkillTigerClaw, SkillRoundKick }; MonkSpecialAttack(ca_target, MonkSPA[MakeRandomInt(0,4)]); - int TripleChance = 25; - + int TripleChance = 25; if (bDoubleSpecialAttack > 100) TripleChance += TripleChance*(100-bDoubleSpecialAttack)/100; @@ -1820,8 +1774,7 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) } } - if(skill_to_use == SkillBackstab) - { + if(skill_to_use == SkillBackstab){ ReuseTime = BackstabReuseTime-1; if (IsRiposte) @@ -1831,8 +1784,7 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) } ReuseTime = (ReuseTime*HasteMod)/100; - if(ReuseTime > 0 && !IsRiposte) - { + if(ReuseTime > 0 && !IsRiposte){ p_timers.Start(pTimerCombatAbility, ReuseTime); } } @@ -1899,8 +1851,7 @@ void Mob::Taunt(NPC* who, bool always_succeed, float chance_bonus) { tauntchance /= 100.0f; - if (tauntchance > MakeRandomFloat(0, 1)) { - + if (tauntchance > MakeRandomFloat(0, 1)) { if (hate_top && hate_top != this){ newhate = (who->GetNPCHate(hate_top) - who->GetNPCHate(this)) + 1; who->CastToNPC()->AddToHateList(this, newhate); @@ -1922,8 +1873,7 @@ void Mob::Taunt(NPC* who, bool always_succeed, float chance_bonus) { if (HasSkillProcs()) TrySkillProc(who, SkillTaunt, TauntReuseTime*1000); - - + if (Success && HasSkillProcSuccess()) TrySkillProc(who, SkillTaunt, TauntReuseTime*1000, true); } @@ -1944,8 +1894,7 @@ void Mob::InstillDoubt(Mob *who) { if(!CombatRange(who)) return; - if(IsClient()) - { + if(IsClient()) { CastToClient()->CheckIncreaseSkill(SkillIntimidation, who, 10); } @@ -1975,14 +1924,12 @@ void Mob::InstillDoubt(Mob *who) { } } -uint32 Mob::TryHeadShot(Mob* defender, SkillUseTypes skillInUse) { - +uint32 Mob::TryHeadShot(Mob* defender, SkillUseTypes skillInUse) { //Only works on YOUR target. if(defender && (defender->GetBodyType() == BT_Humanoid) && !defender->IsClient() && (skillInUse == SkillArchery) && (GetTarget() == defender)) { - uint32 HeadShot_Dmg = aabonuses.HeadShot[1] + spellbonuses.HeadShot[1] + itembonuses.HeadShot[1]; - + uint32 HeadShot_Dmg = aabonuses.HeadShot[1] + spellbonuses.HeadShot[1] + itembonuses.HeadShot[1]; uint8 HeadShot_Level = 0; //Get Highest Headshot Level HeadShot_Level = aabonuses.HSLevel; if (HeadShot_Level < spellbonuses.HSLevel) @@ -1990,8 +1937,7 @@ uint32 Mob::TryHeadShot(Mob* defender, SkillUseTypes skillInUse) { else if (HeadShot_Level < itembonuses.HSLevel) HeadShot_Level = itembonuses.HSLevel; - if(HeadShot_Dmg && HeadShot_Level && (defender->GetLevel() <= HeadShot_Level)){ - + if(HeadShot_Dmg && HeadShot_Level && (defender->GetLevel() <= HeadShot_Level)){ float ProcChance = GetSpecialProcChances(MainRange); if(ProcChance > MakeRandomFloat(0,1)) return HeadShot_Dmg; @@ -2084,7 +2030,7 @@ float Mob::GetAssassinateProcChances(uint16 ReuseTime) ProcChance += ProcChance * ProcBonus / 100.0f; } else { - /*Kayen: Unable to find data on old proc rate of assassinate, no idea if our formula is real or made up.*/ + /* Kayen: Unable to find data on old proc rate of assassinate, no idea if our formula is real or made up. */ ProcChance = (10 + (static_cast(mydex/10) + static_cast(itembonuses.HeroicDEX /10)))/100.0f; } @@ -2097,8 +2043,10 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes if (!CanDoSpecialAttack(other)) return; - //For spells using skill value 98 (feral swipe ect) server sets this to 67 automatically. - //Kayen: This is unlikely to be completely accurate but use OFFENSE skill value for these effects. + /* + For spells using skill value 98 (feral swipe ect) server sets this to 67 automatically. + Kayen: This is unlikely to be completely accurate but use OFFENSE skill value for these effects. + */ if (skillinuse == SkillBegging) skillinuse = SkillOffense; @@ -2107,8 +2055,7 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes int Hand = MainPrimary; if (hate == 0 && weapon_damage > 1) hate = weapon_damage; - if(weapon_damage > 0){ - + if(weapon_damage > 0){ if (focus) //From FcBaseEffects weapon_damage += weapon_damage*focus/100; @@ -2118,12 +2065,10 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes } int32 min_hit = 1; - int32 max_hit = (2*weapon_damage*GetDamageTable(skillinuse)) / 100; - - if(GetLevel() >= 28 && IsWarriorClass() ) - { - int ucDamageBonus = GetWeaponDamageBonus((const Item_Struct*) nullptr ); + int32 max_hit = (2 * weapon_damage*GetDamageTable(skillinuse)) / 100; + if(GetLevel() >= 28 && IsWarriorClass() ) { + int ucDamageBonus = GetWeaponDamageBonus((const Item_Struct*) nullptr ); min_hit += (int) ucDamageBonus; max_hit += (int) ucDamageBonus; hate += ucDamageBonus; @@ -2142,8 +2087,7 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes } } - ApplySpecialAttackMod(skillinuse, max_hit, min_hit); - + ApplySpecialAttackMod(skillinuse, max_hit, min_hit); min_hit += min_hit * GetMeleeMinDamageMod_SE(skillinuse) / 100; if(max_hit < min_hit) @@ -2204,8 +2148,7 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes TrySkillProc(other, skillinuse, ReuseTime, true); } -bool Mob::CanDoSpecialAttack(Mob *other) -{ +bool Mob::CanDoSpecialAttack(Mob *other) { //Make sure everything is valid before doing any attacks. if (!other) { SetTarget(nullptr); @@ -2215,8 +2158,7 @@ bool Mob::CanDoSpecialAttack(Mob *other) if(!GetTarget()) SetTarget(other); - if ((other == nullptr || ((IsClient() && CastToClient()->dead) || (other->IsClient() && other->CastToClient()->dead)) - || HasDied() || (!IsAttackAllowed(other)))) { + if ((other == nullptr || ((IsClient() && CastToClient()->dead) || (other->IsClient() && other->CastToClient()->dead)) || HasDied() || (!IsAttackAllowed(other)))) { return false; } diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index bb1c3c449..b10c436f2 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -139,7 +139,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) if(IsNPC()) { - std::vector args; + std::vector args; args.push_back(&buffslot); int i = parse->EventSpell(EVENT_SPELL_EFFECT_NPC, CastToNPC(), nullptr, spell_id, caster ? caster->GetID() : 0, &args); if(i != 0){ @@ -149,7 +149,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) } else if(IsClient()) { - std::vector args; + std::vector args; args.push_back(&buffslot); int i = parse->EventSpell(EVENT_SPELL_EFFECT_CLIENT, nullptr, CastToClient(), spell_id, caster ? caster->GetID() : 0, &args); if(i != 0){ @@ -3293,7 +3293,7 @@ void Mob::DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caste if(IsNPC()) { - std::vector args; + std::vector args; args.push_back(&ticsremaining); args.push_back(&caster_level); args.push_back(&slot); @@ -3304,7 +3304,7 @@ void Mob::DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caste } else { - std::vector args; + std::vector args; args.push_back(&ticsremaining); args.push_back(&caster_level); args.push_back(&slot); @@ -3654,12 +3654,12 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) } if(IsClient()) { - std::vector args; + std::vector args; args.push_back(&buffs[slot].casterid); parse->EventSpell(EVENT_SPELL_FADE, nullptr, CastToClient(), buffs[slot].spellid, slot, &args); } else if(IsNPC()) { - std::vector args; + std::vector args; args.push_back(&buffs[slot].casterid); parse->EventSpell(EVENT_SPELL_FADE, CastToNPC(), nullptr, buffs[slot].spellid, slot, &args); diff --git a/zone/spells.cpp b/zone/spells.cpp index 4eddf5883..357fafbd4 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -1252,7 +1252,7 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, uint16 slot, } } - if(IsClient()) { + if(IsClient()) { CheckNumHitsRemaining(NUMHIT_MatchingSpells); TrySympatheticProc(target, spell_id); } @@ -1378,7 +1378,7 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce mlog(AA__MESSAGE, "Project Illusion overwrote target caster: %s spell id: %d was ON", GetName(), spell_id); targetType = ST_GroupClientAndPet; } - + if (spell_target && !spell_target->PassCastRestriction(true, spells[spell_id].CastRestriction)){ Message_StringID(13,SPELL_NEED_TAR); return false; @@ -1386,16 +1386,16 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce //Must be out of combat. (If Beneficial checks casters combat state, Deterimental checks targets) if (!spells[spell_id].InCombat && spells[spell_id].OutofCombat){ - if (IsDetrimentalSpell(spell_id)) { - if ( (spell_target->IsNPC() && spell_target->IsEngaged()) || + if (IsDetrimentalSpell(spell_id)) { + if ( (spell_target->IsNPC() && spell_target->IsEngaged()) || (spell_target->IsClient() && spell_target->CastToClient()->GetAggroCount())){ Message_StringID(13,SPELL_NO_EFFECT); //Unsure correct string return false; } } - else if (IsBeneficialSpell(spell_id)) { - if ( (IsNPC() && IsEngaged()) || + else if (IsBeneficialSpell(spell_id)) { + if ( (IsNPC() && IsEngaged()) || (IsClient() && CastToClient()->GetAggroCount())){ if (IsDiscipline(spell_id)) Message_StringID(13,NO_ABILITY_IN_COMBAT); @@ -1409,16 +1409,16 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce //Must be in combat. (If Beneficial checks casters combat state, Deterimental checks targets) else if (spells[spell_id].InCombat && !spells[spell_id].OutofCombat){ - if (IsDetrimentalSpell(spell_id)) { - if ( (spell_target->IsNPC() && !spell_target->IsEngaged()) || + if (IsDetrimentalSpell(spell_id)) { + if ( (spell_target->IsNPC() && !spell_target->IsEngaged()) || (spell_target->IsClient() && !spell_target->CastToClient()->GetAggroCount())){ Message_StringID(13,SPELL_NO_EFFECT); //Unsure correct string return false; } } - else if (IsBeneficialSpell(spell_id)) { - if ( (IsNPC() && !IsEngaged()) || + else if (IsBeneficialSpell(spell_id)) { + if ( (IsNPC() && !IsEngaged()) || (IsClient() && !CastToClient()->GetAggroCount())){ if (IsDiscipline(spell_id)) Message_StringID(13,NO_ABILITY_OUT_OF_COMBAT); @@ -1782,10 +1782,10 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce case ST_PetMaster: { - + Mob *owner = nullptr; - - if (IsPet()) + + if (IsPet()) owner = GetOwner(); else if ((IsNPC() && CastToNPC()->GetSwarmOwner())) owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner()); @@ -1975,7 +1975,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16 if (!TrySpellProjectile(spell_target, spell_id)) return false; } - + else if(!SpellOnTarget(spell_id, spell_target, false, true, resist_adjust, false)) { if(IsBuffSpell(spell_id) && IsBeneficialSpell(spell_id)) { // Prevent mana usage/timers being set for beneficial buffs @@ -2723,7 +2723,7 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, if ((effect2 == SE_BStacker) && (!IsCastonFadeDurationSpell(spellid1) && buffs[buffslot].ticsremaining != 1 && IsEffectInSpell(spellid1, SE_CStacker))) return -1; } - + if (spellbonuses.DStacker[0]) { if ((effect2 == SE_DStacker) && (sp2.effectid[i] <= spellbonuses.DStacker[1])) return -1; @@ -2775,7 +2775,7 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, mlog(SPELLS__STACKING, "%s (%d) blocks effect %d on slot %d below %d, but we do not have that effect on that slot. Ignored.", sp1.name, spellid1, blocked_effect, blocked_slot, blocked_below_value); } - } + } } } else { mlog(SPELLS__STACKING, "%s (%d) and %s (%d) appear to be in the same line, skipping Stacking Overwrite/Blocking checks", @@ -3564,7 +3564,7 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob* spelltar, bool reflect, bool use_r if(spell_effectiveness == 0 || !IsPartialCapableSpell(spell_id) ) { mlog(SPELLS__RESISTS, "Spell %d was completely resisted by %s", spell_id, spelltar->GetName()); - + if (spells[spell_id].resisttype == RESIST_PHYSICAL){ Message_StringID(MT_SpellFailure, PHYSICAL_RESIST_FAIL,spells[spell_id].name); spelltar->Message_StringID(MT_SpellFailure, YOU_RESIST, spells[spell_id].name); @@ -3707,7 +3707,7 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob* spelltar, bool reflect, bool use_r TrySpellTrigger(spelltar, spell_id); TryApplyEffect(spelltar, spell_id); - + if (spelltar->IsAIControlled() && IsDetrimentalSpell(spell_id) && !IsHarmonySpell(spell_id)) { int32 aggro_amount = CheckAggroAmount(spell_id, isproc); mlog(SPELLS__CASTING, "Spell %d cast on %s generated %d hate", spell_id, spelltar->GetName(), aggro_amount); @@ -3733,7 +3733,7 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob* spelltar, bool reflect, bool use_r safe_delete(action_packet); return false; } - + // cause the effects to the target if(!spelltar->SpellEffect(this, spell_id, spell_effectiveness)) { @@ -3746,11 +3746,11 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob* spelltar, bool reflect, bool use_r return false; } - + if (IsDetrimentalSpell(spell_id)) { - + CheckNumHitsRemaining(NUMHIT_OutgoingSpells); - + if (spelltar) spelltar->CheckNumHitsRemaining(NUMHIT_IncomingSpells); } @@ -4408,7 +4408,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use if (CharismaCheck) { - /* + /* Charisma ONLY effects the initial resist check when charm is cast with 10 CHA = -1 Resist mod up to 255 CHA (min ~ 75 cha) Charisma less than ~ 75 gives a postive modifier to resist checks at approximate ratio of -10 CHA = +6 Resist. Mez spells do same initial resist check as a above. @@ -4470,7 +4470,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use if (CharmTick) { int min_charmbreakchance = ((100/RuleI(Spells, CharmBreakCheckChance))/66 * 100)*2; - + if (resist_chance < min_charmbreakchance) resist_chance = min_charmbreakchance; } @@ -4965,68 +4965,56 @@ int Client::FindSpellBookSlotBySpellID(uint16 spellid) { return -1; //default } -bool Client::SpellGlobalCheck(uint16 Spell_ID, uint16 Char_ID) { +bool Client::SpellGlobalCheck(uint16 spell_ID, uint16 char_ID) { - std::string Spell_Global_Name; - int Spell_Global_Value; - int Global_Value; + std::string spell_Global_Name; + int spell_Global_Value; + int global_Value; - char errbuf[MYSQL_ERRMSG_SIZE]; - char *query = 0; - MYSQL_RES *result; - MYSQL_ROW row; - - if (database.RunQuery(query,MakeAnyLenString(&query, "SELECT qglobal, value FROM spell_globals WHERE spellid=%i", Spell_ID), errbuf, &result)) { - safe_delete_array(query); - - if (mysql_num_rows(result) == 1) { - row = mysql_fetch_row(result); - Spell_Global_Name = row[0]; - Spell_Global_Value = atoi(row[1]); - - mysql_free_result(result); - - if (Spell_Global_Name.empty()) { // If the entry in the spell_globals table has nothing set for the qglobal name - return true; - } - else if (database.RunQuery(query,MakeAnyLenString(&query, "SELECT value FROM quest_globals WHERE charid=%i AND name='%s'", Char_ID, Spell_Global_Name.c_str()), errbuf, &result)) { - safe_delete_array(query); - - if (mysql_num_rows(result) == 1) { - row = mysql_fetch_row(result); - - Global_Value = atoi(row[0]); - mysql_free_result(result); - if (Global_Value == Spell_Global_Value) { // If the values match from both tables, allow the spell to be scribed - return true; - } - else if (Global_Value > Spell_Global_Value) { // Check if the qglobal value is greater than the require spellglobal value - return true; - } - else // If no matching result found in qglobals, don't scribe this spell - { - LogFile->write(EQEMuLog::Error, "Char ID: %i Spell_globals Name: '%s' Value: '%i' did not match QGlobal Value: '%i' for Spell ID %i", Char_ID, Spell_Global_Name.c_str(), Spell_Global_Value, Global_Value, Spell_ID); - return false; - } - } - else - LogFile->write(EQEMuLog::Error, "Char ID: %i does not have the Qglobal Name: '%s' for Spell ID %i", Char_ID, Spell_Global_Name.c_str(), Spell_ID); - safe_delete_array(query); - } - else - LogFile->write(EQEMuLog::Error, "Spell ID %i query of spell_globals with Name: '%s' Value: '%i' failed", Spell_ID, Spell_Global_Name.c_str(), Spell_Global_Value); - } - else { - return true; // Spell ID isn't listed in the spells_global table, so it is not restricted from scribing - } - mysql_free_result(result); - } - else { - LogFile->write(EQEMuLog::Error, "Error while querying Spell ID %i spell_globals table query '%s': %s", Spell_ID, query, errbuf); - safe_delete_array(query); + std::string query = StringFormat("SELECT qglobal, value FROM spell_globals " + "WHERE spellid = %i", spell_ID); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error while querying Spell ID %i spell_globals table query '%s': %s", spell_ID, query.c_str(), results.ErrorMessage().c_str()); return false; // Query failed, so prevent spell from scribing just in case - } - return false; // Default is false + } + + if (results.RowCount() != 1) + return true; // Spell ID isn't listed in the spells_global table, so it is not restricted from scribing + + auto row = results.begin(); + spell_Global_Name = row[0]; + spell_Global_Value = atoi(row[1]); + + if (spell_Global_Name.empty()) + return true; // If the entry in the spell_globals table has nothing set for the qglobal name + + query = StringFormat("SELECT value FROM quest_globals " + "WHERE charid = %i AND name = '%s'", + char_ID, spell_Global_Name.c_str()); + results = database.QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Spell ID %i query of spell_globals with Name: '%s' Value: '%i' failed", spell_ID, spell_Global_Name.c_str(), spell_Global_Value); + return false; + } + + if (results.RowCount() != 1) { + LogFile->write(EQEMuLog::Error, "Char ID: %i does not have the Qglobal Name: '%s' for Spell ID %i", char_ID, spell_Global_Name.c_str(), spell_ID); + return false; + } + + row = results.begin(); + + global_Value = atoi(row[0]); + + if (global_Value == spell_Global_Value) + return true; // If the values match from both tables, allow the spell to be scribed + else if (global_Value > spell_Global_Value) + return true; // Check if the qglobal value is greater than the require spellglobal value + + // If no matching result found in qglobals, don't scribe this spell + LogFile->write(EQEMuLog::Error, "Char ID: %i Spell_globals Name: '%s' Value: '%i' did not match QGlobal Value: '%i' for Spell ID %i", char_ID, spell_Global_Name.c_str(), spell_Global_Value, global_Value, spell_ID); + return false; } // TODO get rid of this @@ -5081,7 +5069,7 @@ bool Mob::FindType(uint16 type, bool bOffensive, uint16 threshold) { } bool Mob::IsCombatProc(uint16 spell_id) { - + if (RuleB(Spells, FocusCombatProcs)) return false; @@ -5092,7 +5080,7 @@ bool Mob::IsCombatProc(uint16 spell_id) { { for (int i = 0; i < MAX_PROCS; i++){ - if (PermaProcs[i].spellID == spell_id || SpellProcs[i].spellID == spell_id + if (PermaProcs[i].spellID == spell_id || SpellProcs[i].spellID == spell_id || RangedProcs[i].spellID == spell_id){ return true; } @@ -5150,7 +5138,7 @@ bool Mob::AddDefensiveProc(uint16 spell_id, uint16 iChance, uint16 base_spell_id { if(spell_id == SPELL_UNKNOWN) return(false); - + int i; for (i = 0; i < MAX_PROCS; i++) { if (DefensiveProcs[i].spellID == SPELL_UNKNOWN) { diff --git a/zone/tasks.cpp b/zone/tasks.cpp index 994ebdc5d..3da32c03c 100644 --- a/zone/tasks.cpp +++ b/zone/tasks.cpp @@ -34,18 +34,16 @@ Copyright (C) 2001-2008 EQEMu Development Team (http://eqemulator.net) #include "../common/features.h" #include "quest_parser_collection.h" #include "mob.h" +#include "queryserv.h" +extern QueryServ* QServ; TaskManager::TaskManager() { - for(int i=0; iActivityCount; j++) { @@ -62,11 +60,8 @@ TaskManager::~TaskManager() { } bool TaskManager::LoadTaskSets() { - - const char *TaskSetQuery = "SELECT `id`, `taskid` from `tasksets` WHERE `id` > 0 AND `id` < %i " "AND `taskid` >= 0 AND `taskid` < %i ORDER BY `id`, `taskid` ASC"; - const char *ERR_MYSQLERROR = "[TASKS]Error in TaskManager::LoadTaskSets: %s"; char errbuf[MYSQL_ERRMSG_SIZE]; @@ -1985,12 +1980,17 @@ void ClientTaskState::IncrementDoneCount(Client *c, TaskInformation* Task, int T // Inform the client the task has been updated, both by a chat message c->Message(0, "Your task '%s' has been updated.", Task->Title); - if(Task->Activity[ActivityID].GoalMethod != METHODQUEST) - { + if(Task->Activity[ActivityID].GoalMethod != METHODQUEST) { char buf[24]; snprintf(buf, 23, "%d %d", ActiveTasks[TaskIndex].TaskID, ActiveTasks[TaskIndex].Activity[ActivityID].ActivityID); buf[23] = '\0'; parse->EventPlayer(EVENT_TASK_STAGE_COMPLETE, c, buf, 0); + + /* QS: PlayerLogTaskUpdates :: Update */ + if (RuleB(QueryServ, PlayerLogTaskUpdates)){ + std::string event_desc = StringFormat("Task Stage Complete :: taskid:%i activityid:%i donecount:%i in zoneid:%i instid:%i", ActiveTasks[TaskIndex].TaskID, ActiveTasks[TaskIndex].Activity[ActivityID].ActivityID, ActiveTasks[TaskIndex].Activity[ActivityID].DoneCount, c->GetZoneID(), c->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_Task_Updates, c->CharacterID(), event_desc); + } } // If this task is now complete, the Completed tasks will have been @@ -2002,6 +2002,12 @@ void ClientTaskState::IncrementDoneCount(Client *c, TaskInformation* Task, int T buf[23] = '\0'; parse->EventPlayer(EVENT_TASK_COMPLETE, c, buf, 0); + /* QS: PlayerLogTaskUpdates :: Complete */ + if (RuleB(QueryServ, PlayerLogTaskUpdates)){ + std::string event_desc = StringFormat("Task Complete :: taskid:%i activityid:%i donecount:%i in zoneid:%i instid:%i", ActiveTasks[TaskIndex].TaskID, ActiveTasks[TaskIndex].Activity[ActivityID].ActivityID, ActiveTasks[TaskIndex].Activity[ActivityID].DoneCount, c->GetZoneID(), c->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_Task_Updates, c->CharacterID(), event_desc); + } + taskmanager->SendCompletedTasksToClient(c, this); c->SendTaskActivityComplete(ActiveTasks[TaskIndex].TaskID, 0, TaskIndex, false); taskmanager->SaveClientState(c, this); @@ -2011,6 +2017,7 @@ void ClientTaskState::IncrementDoneCount(Client *c, TaskInformation* Task, int T // If Experience and/or cash rewards are set, reward them from the task even if RewardMethod is METHODQUEST RewardTask(c, Task); //RemoveTask(c, TaskIndex); + } } diff --git a/zone/tradeskills.cpp b/zone/tradeskills.cpp index 4d4edb344..91cb458a0 100644 --- a/zone/tradeskills.cpp +++ b/zone/tradeskills.cpp @@ -34,6 +34,9 @@ #include "../common/string_util.h" #include "../common/rulesys.h" #include "quest_parser_collection.h" +#include "queryserv.h" + +extern QueryServ* QServ; static const SkillUseTypes TradeskillUnknown = Skill1HBlunt; /* an arbitrary non-tradeskill */ @@ -146,7 +149,7 @@ void Object::HandleAugmentation(Client* user, const AugmentItem_Struct* in_augme ItemInst *aug = tobe_auged->GetAugment(slot); if(aug) { - std::vector args; + std::vector args; args.push_back(aug); parse->EventItem(EVENT_AUGMENT_ITEM, user, tobe_auged, nullptr, "", slot, &args); @@ -168,7 +171,7 @@ void Object::HandleAugmentation(Client* user, const AugmentItem_Struct* in_augme const uint32 id = auged_with->GetID(); ItemInst *aug = tobe_auged->GetAugment(in_augment->augment_slot); if(aug) { - std::vector args; + std::vector args; args.push_back(aug); parse->EventItem(EVENT_UNAUGMENT_ITEM, user, tobe_auged, nullptr, "", slot, &args); @@ -1067,7 +1070,7 @@ bool Client::TradeskillExecute(DBTradeskillRecipe_Struct *spec) { if(over_trivial < 0) CheckIncreaseTradeskill(bonusstat, stat_modifier, skillup_modifier, success_modifier, spec->tradeskill); - Message_StringID(4,TRADESKILL_SUCCEED,spec->name.c_str()); + Message_StringID(4, TRADESKILL_SUCCEED, spec->name.c_str()); _log(TRADESKILLS__TRACE, "Tradeskill success"); @@ -1076,16 +1079,24 @@ bool Client::TradeskillExecute(DBTradeskillRecipe_Struct *spec) { //should we check this crap? SummonItem(itr->first, itr->second); item = database.GetItem(itr->first); - if (this->GetGroup()) - { - entity_list.MessageGroup(this,true,MT_Skills,"%s has successfully fashioned %s!",GetName(),item->Name); + if (this->GetGroup()) { + entity_list.MessageGroup(this, true, MT_Skills, "%s has successfully fashioned %s!", GetName(), item->Name); } + + /* QS: Player_Log_Trade_Skill_Events */ + if (RuleB(QueryServ, PlayerLogTradeSkillEvents)){ + std::string event_desc = StringFormat("Success :: fashioned recipe_id:%i tskillid:%i trivial:%i chance:%4.2f in zoneid:%i instid:%i", spec->recipe_id, spec->tradeskill, spec->trivial, chance, this->GetZoneID(), this->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_Trade_Skill_Events, this->CharacterID(), event_desc); + } + if(RuleB(TaskSystem, EnableTaskSystem)) UpdateTasksForItem(ActivityTradeSkill, itr->first, itr->second); ++itr; } return(true); - } else { + } + /* Tradeskill Fail */ + else { success_modifier = 2; // Halves the chance if(over_trivial < 0) @@ -1097,6 +1108,13 @@ bool Client::TradeskillExecute(DBTradeskillRecipe_Struct *spec) { if (this->GetGroup()) { entity_list.MessageGroup(this,true,MT_Skills,"%s was unsuccessful in %s tradeskill attempt.",GetName(),this->GetGender() == 0 ? "his" : this->GetGender() == 1 ? "her" : "its"); + + } + + /* QS: Player_Log_Trade_Skill_Events */ + if (RuleB(QueryServ, PlayerLogTradeSkillEvents)){ + std::string event_desc = StringFormat("Failed :: recipe_id:%i tskillid:%i trivial:%i chance:%4.2f in zoneid:%i instid:%i", spec->recipe_id, spec->tradeskill, spec->trivial, chance, this->GetZoneID(), this->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_Trade_Skill_Events, this->CharacterID(), event_desc); } itr = spec->onfail.begin(); @@ -1106,6 +1124,8 @@ bool Client::TradeskillExecute(DBTradeskillRecipe_Struct *spec) { ++itr; } + /* Salvage Item rolls */ + // Rolls on each item, is possible to return everything int SalvageChance = aabonuses.SalvageChance + itembonuses.SalvageChance + spellbonuses.SalvageChance; // Skip check if not a normal TS or if a quest recipe these should be nofail, but check amyways diff --git a/zone/trading.cpp b/zone/trading.cpp index 1efcbbb33..ea9e36a31 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -22,7 +22,10 @@ #include "../common/rulesys.h" #include "quest_parser_collection.h" #include "worldserver.h" +#include "queryserv.h" + extern WorldServer worldserver; +extern QueryServ* QServ; // The maximum amount of a single bazaar/barter transaction expressed in copper. // Equivalent to 2 Million plat @@ -71,8 +74,8 @@ void Trade::Start(uint32 mob_id, bool initiate_with) } // Add item from a given slot to trade bucket (automatically does bag data too) -void Trade::AddEntity(uint16 from_slot_id, uint16 trade_slot_id, uint32 stack_size) { - // TODO: review for inventory saves +void Trade::AddEntity(uint16 trade_slot_id, uint32 stack_size) { + // TODO: review for inventory saves / consider changing return type to bool so failure can be passed to desync handler if (!owner || !owner->IsClient()) { // This should never happen @@ -121,7 +124,7 @@ void Trade::AddEntity(uint16 from_slot_id, uint16 trade_slot_id, uint32 stack_si if (_stack_size > 0) inst->SetCharges(_stack_size); else - client->DeleteItemInInventory(from_slot_id); + client->DeleteItemInInventory(MainCursor); SendItemData(inst2, trade_slot_id); } @@ -136,7 +139,7 @@ void Trade::AddEntity(uint16 from_slot_id, uint16 trade_slot_id, uint32 stack_si _log(TRADING__HOLDER, "%s added item '%s' to trade slot %i", owner->GetName(), inst->GetItem()->Name, trade_slot_id); client->PutItemInInventory(trade_slot_id, *inst); - client->DeleteItemInInventory(from_slot_id); + client->DeleteItemInInventory(MainCursor); } } @@ -316,200 +319,464 @@ void Trade::DumpTrade() #endif void Client::ResetTrade() { - const Item_Struct* TempItem = 0; - ItemInst* ins; - int x; AddMoneyToPP(trade->cp, trade->sp, trade->gp, trade->pp, true); - for(x = EmuConstants::TRADE_BEGIN; x <= EmuConstants::TRADE_END; x++) - { - TempItem = 0; - ins = GetInv().GetItem(x); - if (ins) - TempItem = ins->GetItem(); - if (TempItem) - { - bool is_arrow = (TempItem->ItemType == ItemTypeArrow) ? true : false; - int freeslotid = GetInv().FindFreeSlot(ins->IsType(ItemClassContainer), true, TempItem->Size, is_arrow); - if (freeslotid == INVALID_INDEX) - { - DropInst(ins); + + // step 1: process bags + for (int16 trade_slot = EmuConstants::TRADE_BEGIN; trade_slot <= EmuConstants::TRADE_END; ++trade_slot) { + const ItemInst* inst = m_inv[trade_slot]; + + if (inst && inst->IsType(ItemClassContainer)) { + int16 free_slot = m_inv.FindFreeSlotForTradeItem(inst); + + if (free_slot != INVALID_INDEX) { + PutItemInInventory(free_slot, *inst); + SendItemPacket(free_slot, inst, ItemPacketTrade); } - else - { - PutItemInInventory(freeslotid, *ins); - SendItemPacket(freeslotid, ins, ItemPacketTrade); + else { + DropInst(inst); } - DeleteItemInInventory(x); + + DeleteItemInInventory(trade_slot); + } + } + + // step 2a: process stackables + for (int16 trade_slot = EmuConstants::TRADE_BEGIN; trade_slot <= EmuConstants::TRADE_END; ++trade_slot) { + ItemInst* inst = GetInv().GetItem(trade_slot); + + if (inst && inst->IsStackable()) { + while (true) { + // there's no built-in safety check against an infinite loop..but, it should break on one of the conditional checks + int16 free_slot = m_inv.FindFreeSlotForTradeItem(inst); + + if ((free_slot == MainCursor) || (free_slot == INVALID_INDEX)) + break; + + ItemInst* partial_inst = GetInv().GetItem(free_slot); + + if (!partial_inst) + break; + + if (partial_inst->GetID() != inst->GetID()) { + _log(TRADING__ERROR, "Client::ResetTrade() - an incompatible location reference was returned by Inventory::FindFreeSlotForTradeItem()"); + + break; + } + + if ((partial_inst->GetCharges() + inst->GetCharges()) > partial_inst->GetItem()->StackSize) { + int16 new_charges = (partial_inst->GetCharges() + inst->GetCharges()) - partial_inst->GetItem()->StackSize; + + partial_inst->SetCharges(partial_inst->GetItem()->StackSize); + inst->SetCharges(new_charges); + } + else { + partial_inst->SetCharges(partial_inst->GetCharges() + inst->GetCharges()); + inst->SetCharges(0); + } + + PutItemInInventory(free_slot, *partial_inst); + SendItemPacket(free_slot, partial_inst, ItemPacketTrade); + + if (inst->GetCharges() == 0) { + DeleteItemInInventory(trade_slot); + + break; + } + } + } + } + + // step 2b: adjust trade stack bias + // (if any partial stacks exist before the final stack, FindFreeSlotForTradeItem() will return that slot in step 3 and an overwrite will occur) + for (int16 trade_slot = EmuConstants::TRADE_END; trade_slot >= EmuConstants::TRADE_BEGIN; --trade_slot) { + ItemInst* inst = GetInv().GetItem(trade_slot); + + if (inst && inst->IsStackable()) { + for (int16 bias_slot = EmuConstants::TRADE_BEGIN; bias_slot <= EmuConstants::TRADE_END; ++bias_slot) { + if (bias_slot >= trade_slot) + break; + + ItemInst* bias_inst = GetInv().GetItem(bias_slot); + + if (!bias_inst || (bias_inst->GetID() != inst->GetID()) || (bias_inst->GetCharges() >= bias_inst->GetItem()->StackSize)) + continue; + + if ((bias_inst->GetCharges() + inst->GetCharges()) > bias_inst->GetItem()->StackSize) { + int16 new_charges = (bias_inst->GetCharges() + inst->GetCharges()) - bias_inst->GetItem()->StackSize; + + bias_inst->SetCharges(bias_inst->GetItem()->StackSize); + inst->SetCharges(new_charges); + } + else { + bias_inst->SetCharges(bias_inst->GetCharges() + inst->GetCharges()); + inst->SetCharges(0); + } + + if (inst->GetCharges() == 0) { + DeleteItemInInventory(trade_slot); + + break; + } + } + } + } + + // step 3: process everything else + for (int16 trade_slot = EmuConstants::TRADE_BEGIN; trade_slot <= EmuConstants::TRADE_END; ++trade_slot) { + const ItemInst* inst = m_inv[trade_slot]; + + if (inst) { + int16 free_slot = m_inv.FindFreeSlotForTradeItem(inst); + + if (free_slot != INVALID_INDEX) { + PutItemInInventory(free_slot, *inst); + SendItemPacket(free_slot, inst, ItemPacketTrade); + } + else { + DropInst(inst); + } + + DeleteItemInInventory(trade_slot); } } } -void Client::FinishTrade(Mob* tradingWith, ServerPacket* qspack, bool finalizer) { - +void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, std::list* event_details) { if(tradingWith && tradingWith->IsClient()) { Client* other = tradingWith->CastToClient(); + QSPlayerLogTrade_Struct* qs_audit = nullptr; + bool qs_log = false; if(other) { mlog(TRADING__CLIENT, "Finishing trade with client %s", other->GetName()); - int16 slot_id; - const Item_Struct* item = nullptr; - QSPlayerLogTrade_Struct* qsaudit = nullptr; - bool QSPLT = false; - + this->AddMoneyToPP(other->trade->cp, other->trade->sp, other->trade->gp, other->trade->pp, true); + + // step 0: pre-processing // QS code - if(qspack && RuleB(QueryServ, PlayerLogTrades)) { - qsaudit = (QSPlayerLogTrade_Struct*) qspack->pBuffer; - QSPLT = true; + if (RuleB(QueryServ, PlayerLogTrades) && event_entry && event_details) { + qs_audit = (QSPlayerLogTrade_Struct*)event_entry; + qs_log = true; + + if (finalizer) { + qs_audit->char2_id = this->character_id; - if(finalizer) { qsaudit->char2_id = this->character_id; } - else { qsaudit->char1_id = this->character_id; } + qs_audit->char2_money.platinum = this->trade->pp; + qs_audit->char2_money.gold = this->trade->gp; + qs_audit->char2_money.silver = this->trade->sp; + qs_audit->char2_money.copper = this->trade->cp; + } + else { + qs_audit->char1_id = this->character_id; + + qs_audit->char1_money.platinum = this->trade->pp; + qs_audit->char1_money.gold = this->trade->gp; + qs_audit->char1_money.silver = this->trade->sp; + qs_audit->char1_money.copper = this->trade->cp; + } } - // Move each trade slot into free inventory slot - for(int16 i = EmuConstants::TRADE_BEGIN; i <= EmuConstants::TRADE_END; i++){ - const ItemInst* inst = m_inv[i]; - uint16 parent_offset = 0; + // step 1: process bags + for (int16 trade_slot = EmuConstants::TRADE_BEGIN; trade_slot <= EmuConstants::TRADE_END; ++trade_slot) { + const ItemInst* inst = m_inv[trade_slot]; - if(inst == nullptr) { continue; } + if (inst && inst->IsType(ItemClassContainer)) { + mlog(TRADING__CLIENT, "Giving container %s (%d) in slot %d to %s", inst->GetItem()->Name, inst->GetItem()->ID, trade_slot, other->GetName()); - mlog(TRADING__CLIENT, "Giving %s (%d) in slot %d to %s", inst->GetItem()->Name, inst->GetItem()->ID, i, other->GetName()); + // TODO: need to check bag items/augments for no drop..everything for attuned... + if (inst->GetItem()->NoDrop != 0 || Admin() >= RuleI(Character, MinStatusForNoDropExemptions) || RuleI(World, FVNoDropFlag) == 1 || other == this) { + int16 free_slot = other->GetInv().FindFreeSlotForTradeItem(inst); - /// Log Player Trades through QueryServ if Rule Enabled - if(QSPLT) { - uint16 item_count = qsaudit->char1_count + qsaudit->char2_count; - parent_offset = item_count; + if (free_slot != INVALID_INDEX) { + if (other->PutItemInInventory(free_slot, *inst, true)) { + mlog(TRADING__CLIENT, "Container %s (%d) successfully transferred, deleting from trade slot.", inst->GetItem()->Name, inst->GetItem()->ID); + if (qs_log) { + QSTradeItems_Struct* detail = new QSTradeItems_Struct; - qsaudit->items[item_count].from_id = this->character_id; - qsaudit->items[item_count].from_slot = i; - qsaudit->items[item_count].to_id = other->CharacterID(); - qsaudit->items[item_count].to_slot = 0; - qsaudit->items[item_count].item_id = inst->GetID(); - qsaudit->items[item_count].charges = inst->GetCharges(); - qsaudit->items[item_count].aug_1 = inst->GetAugmentItemID(1); - qsaudit->items[item_count].aug_2 = inst->GetAugmentItemID(2); - qsaudit->items[item_count].aug_3 = inst->GetAugmentItemID(3); - qsaudit->items[item_count].aug_4 = inst->GetAugmentItemID(4); - qsaudit->items[item_count].aug_5 = inst->GetAugmentItemID(5); + detail->from_id = this->character_id; + detail->from_slot = trade_slot; + detail->to_id = other->CharacterID(); + detail->to_slot = free_slot; + detail->item_id = inst->GetID(); + detail->charges = 1; + detail->aug_1 = inst->GetAugmentItemID(1); + detail->aug_2 = inst->GetAugmentItemID(2); + detail->aug_3 = inst->GetAugmentItemID(3); + detail->aug_4 = inst->GetAugmentItemID(4); + detail->aug_5 = inst->GetAugmentItemID(5); - if(finalizer) { qsaudit->char2_count++; } - else { qsaudit->char1_count++; } + event_details->push_back(detail); + + if (finalizer) + qs_audit->char2_count += detail->charges; + else + qs_audit->char1_count += detail->charges; - if(inst->IsType(ItemClassContainer)) { - // Pseudo-Slot ID's are generated based on how the db saves bag items... - for(uint8 j = SUB_BEGIN; j < inst->GetItem()->BagSlots; j++) { - const ItemInst* baginst = inst->GetItem(j); + //for (uint8 sub_slot = SUB_BEGIN; ((sub_slot < inst->GetItem()->BagSlots) && (sub_slot < EmuConstants::ITEM_CONTAINER_SIZE)); ++sub_slot) { + for (uint8 sub_slot = SUB_BEGIN; (sub_slot < EmuConstants::ITEM_CONTAINER_SIZE); ++sub_slot) { // this is to catch ALL items + const ItemInst* bag_inst = inst->GetItem(sub_slot); - if(baginst == nullptr) { continue; } + if (bag_inst) { + detail = new QSTradeItems_Struct; - int16 k=Inventory::CalcSlotId(i, j); - item_count = qsaudit->char1_count + qsaudit->char2_count; + detail->from_id = this->character_id; + detail->from_slot = Inventory::CalcSlotId(trade_slot, sub_slot); + detail->to_id = other->CharacterID(); + detail->to_slot = Inventory::CalcSlotId(free_slot, sub_slot); + detail->item_id = bag_inst->GetID(); + detail->charges = (!bag_inst->IsStackable() ? 1 : bag_inst->GetCharges()); + detail->aug_1 = bag_inst->GetAugmentItemID(1); + detail->aug_2 = bag_inst->GetAugmentItemID(2); + detail->aug_3 = bag_inst->GetAugmentItemID(3); + detail->aug_4 = bag_inst->GetAugmentItemID(4); + detail->aug_5 = bag_inst->GetAugmentItemID(5); - qsaudit->items[item_count].from_id = this->character_id; - qsaudit->items[item_count].from_slot = k; - qsaudit->items[item_count].to_id = other->CharacterID(); - qsaudit->items[item_count].to_slot = 0; - qsaudit->items[item_count].item_id = baginst->GetID(); - qsaudit->items[item_count].charges = baginst->GetCharges(); - qsaudit->items[item_count].aug_1 = baginst->GetAugmentItemID(1); - qsaudit->items[item_count].aug_2 = baginst->GetAugmentItemID(2); - qsaudit->items[item_count].aug_3 = baginst->GetAugmentItemID(3); - qsaudit->items[item_count].aug_4 = baginst->GetAugmentItemID(4); - qsaudit->items[item_count].aug_5 = baginst->GetAugmentItemID(5); + event_details->push_back(detail); - if(finalizer) { qsaudit->char2_count++; } - else { qsaudit->char1_count++; } - } - } - } - - if (inst->GetItem()->NoDrop != 0 || Admin() >= RuleI(Character, MinStatusForNoDropExemptions) || RuleI(World, FVNoDropFlag) == 1 || other == this) { - bool is_arrow = (inst->GetItem()->ItemType == ItemTypeArrow) ? true : false; - slot_id = other->GetInv().FindFreeSlot(inst->IsType(ItemClassContainer), true, inst->GetItem()->Size, is_arrow); - - mlog(TRADING__CLIENT, "Trying to put %s (%d) into slot %d", inst->GetItem()->Name, inst->GetItem()->ID, slot_id); - - if(other->PutItemInInventory(slot_id, *inst, true)) { - mlog(TRADING__CLIENT, "Item %s (%d) successfully transfered, deleting from trade slot.", inst->GetItem()->Name, inst->GetItem()->ID); - - if(QSPLT) { - qsaudit->items[parent_offset].to_slot = slot_id; - - if(inst->IsType(ItemClassContainer)) { - for(uint8 bagslot_idx = SUB_BEGIN; bagslot_idx < inst->GetItem()->BagSlots; bagslot_idx++) { - const ItemInst* bag_inst = inst->GetItem(bagslot_idx); - - if(bag_inst == nullptr) { continue; } - int16 to_bagslot_id = Inventory::CalcSlotId(slot_id, bagslot_idx); - - qsaudit->items[++parent_offset].to_slot = to_bagslot_id; + if (finalizer) + qs_audit->char2_count += detail->charges; + else + qs_audit->char1_count += detail->charges; + } + } } } + else { + mlog(TRADING__ERROR, "Transfer of container %s (%d) to %s failed, returning to giver.", inst->GetItem()->Name, inst->GetItem()->ID, other->GetName()); + PushItemOnCursor(*inst, true); + } + } + else { + mlog(TRADING__ERROR, "%s's inventory is full, returning container %s (%d) to giver.", other->GetName(), inst->GetItem()->Name, inst->GetItem()->ID); + PushItemOnCursor(*inst, true); } } else { + mlog(TRADING__ERROR, "Container %s (%d) is NoDrop, returning to giver.", inst->GetItem()->Name, inst->GetItem()->ID); PushItemOnCursor(*inst, true); - mlog(TRADING__ERROR, "Unable to give item %d (%d) to %s, returning to giver.", inst->GetItem()->Name, inst->GetItem()->ID, other->GetName()); - - if(QSPLT) { - qsaudit->items[parent_offset].to_id = this->character_id; - qsaudit->items[parent_offset].to_slot = MainCursor; - - if(inst->IsType(ItemClassContainer)) { - for(uint8 bagslot_idx = SUB_BEGIN; bagslot_idx < inst->GetItem()->BagSlots; bagslot_idx++) { - const ItemInst* bag_inst = inst->GetItem(bagslot_idx); - - if(bag_inst == nullptr) { continue; } - int16 to_bagslot_id = Inventory::CalcSlotId(MainCursor, bagslot_idx); - - qsaudit->items[++parent_offset].to_id = this->character_id; - qsaudit->items[parent_offset].to_slot = to_bagslot_id; - } - } - } } - DeleteItemInInventory(i); + DeleteItemInInventory(trade_slot); } - else { - PushItemOnCursor(*inst, true); - DeleteItemInInventory(i); + } - if(QSPLT) { - qsaudit->items[parent_offset].to_id = this->character_id; - qsaudit->items[parent_offset].to_slot = MainCursor; + // step 2a: process stackables + for (int16 trade_slot = EmuConstants::TRADE_BEGIN; trade_slot <= EmuConstants::TRADE_END; ++trade_slot) { + ItemInst* inst = GetInv().GetItem(trade_slot); - if(inst->IsType(ItemClassContainer)) { - for(uint8 bagslot_idx = SUB_BEGIN; bagslot_idx < inst->GetItem()->BagSlots; bagslot_idx++) { - const ItemInst* bag_inst = inst->GetItem(bagslot_idx); + if (inst && inst->IsStackable()) { + while (true) { + // there's no built-in safety check against an infinite loop..but, it should break on one of the conditional checks + int16 partial_slot = other->GetInv().FindFreeSlotForTradeItem(inst); - if(bag_inst == nullptr) { continue; } - int16 to_bagslot_id = Inventory::CalcSlotId(MainCursor, bagslot_idx); + if ((partial_slot == MainCursor) || (partial_slot == INVALID_INDEX)) + break; - qsaudit->items[++parent_offset].to_id = this->character_id; - qsaudit->items[parent_offset].to_slot = to_bagslot_id; + ItemInst* partial_inst = other->GetInv().GetItem(partial_slot); + + if (!partial_inst) + break; + + if (partial_inst->GetID() != inst->GetID()) { + _log(TRADING__ERROR, "Client::ResetTrade() - an incompatible location reference was returned by Inventory::FindFreeSlotForTradeItem()"); + break; + } + + int16 old_charges = inst->GetCharges(); + int16 partial_charges = partial_inst->GetCharges(); + + if ((partial_inst->GetCharges() + inst->GetCharges()) > partial_inst->GetItem()->StackSize) { + int16 new_charges = (partial_inst->GetCharges() + inst->GetCharges()) - partial_inst->GetItem()->StackSize; + + partial_inst->SetCharges(partial_inst->GetItem()->StackSize); + inst->SetCharges(new_charges); + } + else { + partial_inst->SetCharges(partial_inst->GetCharges() + inst->GetCharges()); + inst->SetCharges(0); + } + + mlog(TRADING__CLIENT, "Transferring partial stack %s (%d) in slot %d to %s", inst->GetItem()->Name, inst->GetItem()->ID, trade_slot, other->GetName()); + + if (other->PutItemInInventory(partial_slot, *partial_inst, true)) { + mlog(TRADING__CLIENT, "Partial stack %s (%d) successfully transferred, deleting %i charges from trade slot.", + inst->GetItem()->Name, inst->GetItem()->ID, (old_charges - inst->GetCharges())); + if (qs_log) { + QSTradeItems_Struct* detail = new QSTradeItems_Struct; + + detail->from_id = this->character_id; + detail->from_slot = trade_slot; + detail->to_id = other->CharacterID(); + detail->to_slot = partial_slot; + detail->item_id = inst->GetID(); + detail->charges = (old_charges - inst->GetCharges()); + detail->aug_1 = 0; + detail->aug_2 = 0; + detail->aug_3 = 0; + detail->aug_4 = 0; + detail->aug_5 = 0; + + event_details->push_back(detail); + + if (finalizer) + qs_audit->char2_count += detail->charges; + else + qs_audit->char1_count += detail->charges; } } + else { + mlog(TRADING__ERROR, "Transfer of partial stack %s (%d) to %s failed, returning %i charges to trade slot.", + inst->GetItem()->Name, inst->GetItem()->ID, other->GetName(), (old_charges - inst->GetCharges())); + + inst->SetCharges(old_charges); + partial_inst->SetCharges(partial_charges); + break; + } + + if (inst->GetCharges() == 0) { + DeleteItemInInventory(trade_slot); + break; + } } } } - // Money - look into how NPC's receive cash - this->AddMoneyToPP(other->trade->cp, other->trade->sp, other->trade->gp, other->trade->pp, true); + // step 2b: adjust trade stack bias + // (if any partial stacks exist before the final stack, FindFreeSlotForTradeItem() will return that slot in step 3 and an overwrite will occur) + for (int16 trade_slot = EmuConstants::TRADE_END; trade_slot >= EmuConstants::TRADE_BEGIN; --trade_slot) { + ItemInst* inst = GetInv().GetItem(trade_slot); - // This is currently setup to show character offers, not receipts - if(QSPLT) { - if(finalizer) { - qsaudit->char2_money.platinum = this->trade->pp; - qsaudit->char2_money.gold = this->trade->gp; - qsaudit->char2_money.silver = this->trade->sp; - qsaudit->char2_money.copper = this->trade->cp; + if (inst && inst->IsStackable()) { + for (int16 bias_slot = EmuConstants::TRADE_BEGIN; bias_slot <= EmuConstants::TRADE_END; ++bias_slot) { + if (bias_slot >= trade_slot) + break; + + ItemInst* bias_inst = GetInv().GetItem(bias_slot); + + if (!bias_inst || (bias_inst->GetID() != inst->GetID()) || (bias_inst->GetCharges() >= bias_inst->GetItem()->StackSize)) + continue; + + int16 old_charges = inst->GetCharges(); + + if ((bias_inst->GetCharges() + inst->GetCharges()) > bias_inst->GetItem()->StackSize) { + int16 new_charges = (bias_inst->GetCharges() + inst->GetCharges()) - bias_inst->GetItem()->StackSize; + + bias_inst->SetCharges(bias_inst->GetItem()->StackSize); + inst->SetCharges(new_charges); + } + else { + bias_inst->SetCharges(bias_inst->GetCharges() + inst->GetCharges()); + inst->SetCharges(0); + } + + if (qs_log) { + QSTradeItems_Struct* detail = new QSTradeItems_Struct; + + detail->from_id = this->character_id; + detail->from_slot = trade_slot; + detail->to_id = this->character_id; + detail->to_slot = bias_slot; + detail->item_id = inst->GetID(); + detail->charges = (old_charges - inst->GetCharges()); + detail->aug_1 = 0; + detail->aug_2 = 0; + detail->aug_3 = 0; + detail->aug_4 = 0; + detail->aug_5 = 0; + + event_details->push_back(detail); + } + + if (inst->GetCharges() == 0) { + DeleteItemInInventory(trade_slot); + break; + } + } } - else { - qsaudit->char1_money.platinum = this->trade->pp; - qsaudit->char1_money.gold = this->trade->gp; - qsaudit->char1_money.silver = this->trade->sp; - qsaudit->char1_money.copper = this->trade->cp; + } + + // step 3: process everything else + for (int16 trade_slot = EmuConstants::TRADE_BEGIN; trade_slot <= EmuConstants::TRADE_END; ++trade_slot) { + const ItemInst* inst = m_inv[trade_slot]; + + if (inst) { + mlog(TRADING__CLIENT, "Giving item %s (%d) in slot %d to %s", inst->GetItem()->Name, inst->GetItem()->ID, trade_slot, other->GetName()); + + // TODO: need to check bag items/augments for no drop..everything for attuned... + if (inst->GetItem()->NoDrop != 0 || Admin() >= RuleI(Character, MinStatusForNoDropExemptions) || RuleI(World, FVNoDropFlag) == 1 || other == this) { + int16 free_slot = other->GetInv().FindFreeSlotForTradeItem(inst); + + if (free_slot != INVALID_INDEX) { + if (other->PutItemInInventory(free_slot, *inst, true)) { + mlog(TRADING__CLIENT, "Item %s (%d) successfully transferred, deleting from trade slot.", inst->GetItem()->Name, inst->GetItem()->ID); + if (qs_log) { + QSTradeItems_Struct* detail = new QSTradeItems_Struct; + + detail->from_id = this->character_id; + detail->from_slot = trade_slot; + detail->to_id = other->CharacterID(); + detail->to_slot = free_slot; + detail->item_id = inst->GetID(); + detail->charges = (!inst->IsStackable() ? 1 : inst->GetCharges()); + detail->aug_1 = inst->GetAugmentItemID(1); + detail->aug_2 = inst->GetAugmentItemID(2); + detail->aug_3 = inst->GetAugmentItemID(3); + detail->aug_4 = inst->GetAugmentItemID(4); + detail->aug_5 = inst->GetAugmentItemID(5); + + event_details->push_back(detail); + + if (finalizer) + qs_audit->char2_count += detail->charges; + else + qs_audit->char1_count += detail->charges; + + // 'step 3' should never really see containers..but, just in case... + //for (uint8 sub_slot = SUB_BEGIN; ((sub_slot < inst->GetItem()->BagSlots) && (sub_slot < EmuConstants::ITEM_CONTAINER_SIZE)); ++sub_slot) { + for (uint8 sub_slot = SUB_BEGIN; (sub_slot < EmuConstants::ITEM_CONTAINER_SIZE); ++sub_slot) { // this is to catch ALL items + const ItemInst* bag_inst = inst->GetItem(sub_slot); + + if (bag_inst) { + detail = new QSTradeItems_Struct; + + detail->from_id = this->character_id; + detail->from_slot = trade_slot; + detail->to_id = other->CharacterID(); + detail->to_slot = free_slot; + detail->item_id = bag_inst->GetID(); + detail->charges = (!bag_inst->IsStackable() ? 1 : bag_inst->GetCharges()); + detail->aug_1 = bag_inst->GetAugmentItemID(1); + detail->aug_2 = bag_inst->GetAugmentItemID(2); + detail->aug_3 = bag_inst->GetAugmentItemID(3); + detail->aug_4 = bag_inst->GetAugmentItemID(4); + detail->aug_5 = bag_inst->GetAugmentItemID(5); + + event_details->push_back(detail); + + if (finalizer) + qs_audit->char2_count += detail->charges; + else + qs_audit->char1_count += detail->charges; + } + } + } + } + else { + mlog(TRADING__ERROR, "Transfer of Item %s (%d) to %s failed, returning to giver.", inst->GetItem()->Name, inst->GetItem()->ID, other->GetName()); + PushItemOnCursor(*inst, true); + } + } + else { + mlog(TRADING__ERROR, "%s's inventory is full, returning item %s (%d) to giver.", other->GetName(), inst->GetItem()->Name, inst->GetItem()->ID); + PushItemOnCursor(*inst, true); + } + } + else { + mlog(TRADING__ERROR, "Item %s (%d) is NoDrop, returning to giver.", inst->GetItem()->Name, inst->GetItem()->ID); + PushItemOnCursor(*inst, true); + } + + DeleteItemInInventory(trade_slot); } } @@ -517,62 +784,72 @@ void Client::FinishTrade(Mob* tradingWith, ServerPacket* qspack, bool finalizer) } } else if(tradingWith && tradingWith->IsNPC()) { - QSPlayerLogHandin_Struct* qsaudit = nullptr; - bool QSPLH = false; + QSPlayerLogHandin_Struct* qs_audit = nullptr; + bool qs_log = false; // QS code - if(qspack && RuleB(QueryServ, PlayerLogTrades)) { + if(RuleB(QueryServ, PlayerLogTrades) && event_entry && event_details) { // Currently provides only basic functionality. Calling method will also // need to be modified before item returns and rewards can be logged. -U - qsaudit = (QSPlayerLogHandin_Struct*) qspack->pBuffer; - QSPLH = true; + qs_audit = (QSPlayerLogHandin_Struct*)event_entry; + qs_log = true; - qsaudit->quest_id = 0; - qsaudit->char_id = character_id; - qsaudit->char_money.platinum = trade->pp; - qsaudit->char_money.gold = trade->gp; - qsaudit->char_money.silver = trade->sp; - qsaudit->char_money.copper = trade->cp; - qsaudit->char_count = 0; - qsaudit->npc_id = tradingWith->GetNPCTypeID(); - qsaudit->npc_money.platinum = 0; - qsaudit->npc_money.gold = 0; - qsaudit->npc_money.silver = 0; - qsaudit->npc_money.copper = 0; - qsaudit->npc_count = 0; + qs_audit->quest_id = 0; + qs_audit->char_id = character_id; + qs_audit->char_money.platinum = trade->pp; + qs_audit->char_money.gold = trade->gp; + qs_audit->char_money.silver = trade->sp; + qs_audit->char_money.copper = trade->cp; + qs_audit->char_count = 0; + qs_audit->npc_id = tradingWith->GetNPCTypeID(); + qs_audit->npc_money.platinum = 0; + qs_audit->npc_money.gold = 0; + qs_audit->npc_money.silver = 0; + qs_audit->npc_money.copper = 0; + qs_audit->npc_count = 0; } - if(QSPLH) { // This can be incoporated below when revisions are made -U - for(int16 slot_id = EmuConstants::TRADE_BEGIN; slot_id <= EmuConstants::TRADE_NPC_END; slot_id++) { - const ItemInst* trade_inst = m_inv[slot_id]; + if(qs_log) { // This can be incorporated below when revisions are made -U + for (int16 trade_slot = EmuConstants::TRADE_BEGIN; trade_slot <= EmuConstants::TRADE_NPC_END; ++trade_slot) { + const ItemInst* trade_inst = m_inv[trade_slot]; if(trade_inst) { - strcpy(qsaudit->items[qsaudit->char_count].action_type, "HANDIN"); + QSHandinItems_Struct* detail = new QSHandinItems_Struct; - qsaudit->items[qsaudit->char_count].char_slot = slot_id; - qsaudit->items[qsaudit->char_count].item_id = trade_inst->GetID(); - qsaudit->items[qsaudit->char_count].charges = trade_inst->GetCharges(); - qsaudit->items[qsaudit->char_count].aug_1 = trade_inst->GetAugmentItemID(1); - qsaudit->items[qsaudit->char_count].aug_2 = trade_inst->GetAugmentItemID(2); - qsaudit->items[qsaudit->char_count].aug_3 = trade_inst->GetAugmentItemID(3); - qsaudit->items[qsaudit->char_count].aug_4 = trade_inst->GetAugmentItemID(4); - qsaudit->items[qsaudit->char_count++].aug_5 = trade_inst->GetAugmentItemID(5); + strcpy(detail->action_type, "HANDIN"); + + detail->char_slot = trade_slot; + detail->item_id = trade_inst->GetID(); + detail->charges = (!trade_inst->IsStackable() ? 1 : trade_inst->GetCharges()); + detail->aug_1 = trade_inst->GetAugmentItemID(1); + detail->aug_2 = trade_inst->GetAugmentItemID(2); + detail->aug_3 = trade_inst->GetAugmentItemID(3); + detail->aug_4 = trade_inst->GetAugmentItemID(4); + detail->aug_5 = trade_inst->GetAugmentItemID(5); + + event_details->push_back(detail); + qs_audit->char_count += detail->charges; if(trade_inst->IsType(ItemClassContainer)) { - for(uint8 bag_idx = SUB_BEGIN; bag_idx < trade_inst->GetItem()->BagSlots; bag_idx++) { - const ItemInst* trade_baginst = trade_inst->GetItem(bag_idx); + for (uint8 sub_slot = SUB_BEGIN; sub_slot < trade_inst->GetItem()->BagSlots; ++sub_slot) { + const ItemInst* trade_baginst = trade_inst->GetItem(sub_slot); if(trade_baginst) { - strcpy(qsaudit->items[qsaudit->char_count].action_type, "HANDIN"); + detail = new QSHandinItems_Struct; - qsaudit->items[qsaudit->char_count].char_slot = Inventory::CalcSlotId(slot_id, bag_idx); - qsaudit->items[qsaudit->char_count].item_id = trade_baginst->GetID(); - qsaudit->items[qsaudit->char_count].charges = trade_baginst->GetCharges(); - qsaudit->items[qsaudit->char_count].aug_1 = trade_baginst->GetAugmentItemID(1); - qsaudit->items[qsaudit->char_count].aug_2 = trade_baginst->GetAugmentItemID(2); - qsaudit->items[qsaudit->char_count].aug_3 = trade_baginst->GetAugmentItemID(3); - qsaudit->items[qsaudit->char_count].aug_4 = trade_baginst->GetAugmentItemID(4); - qsaudit->items[qsaudit->char_count++].aug_5 = trade_baginst->GetAugmentItemID(5); + strcpy(detail->action_type, "HANDIN"); + + detail->char_slot = Inventory::CalcSlotId(trade_slot, sub_slot); + detail->item_id = trade_baginst->GetID(); + detail->charges = (!trade_inst->IsStackable() ? 1 : trade_inst->GetCharges()); + detail->aug_1 = trade_baginst->GetAugmentItemID(1); + detail->aug_2 = trade_baginst->GetAugmentItemID(2); + detail->aug_3 = trade_baginst->GetAugmentItemID(3); + detail->aug_4 = trade_baginst->GetAugmentItemID(4); + detail->aug_5 = trade_baginst->GetAugmentItemID(5); + + event_details->push_back(detail); + qs_audit->char_count += detail->charges; } } } @@ -586,7 +863,7 @@ void Client::FinishTrade(Mob* tradingWith, ServerPacket* qspack, bool finalizer) quest_npc = true; } - std::vector item_list; + std::vector item_list; uint32 items[4] = { 0 }; for(int i = EmuConstants::TRADE_BEGIN; i <= EmuConstants::TRADE_NPC_END; ++i) { ItemInst *inst = m_inv.GetItem(i); diff --git a/zone/trap.cpp b/zone/trap.cpp index badd14c6c..44c177118 100644 --- a/zone/trap.cpp +++ b/zone/trap.cpp @@ -262,45 +262,36 @@ Mob* EntityList::GetTrapTrigger(Trap* trap) { //todo: rewrite this to not need direct access to trap members. bool ZoneDatabase::LoadTraps(const char* zonename, int16 version) { - char errbuf[MYSQL_ERRMSG_SIZE]; - char *query = 0; - MYSQL_RES *result; - MYSQL_ROW row; - // int char_num = 0; - unsigned long* lengths; - - if (RunQuery(query, MakeAnyLenString(&query, "SELECT id,x,y,z,effect,effectvalue,effectvalue2,skill,maxzdiff,radius,chance,message,respawn_time,respawn_var,level FROM traps WHERE zone='%s' AND version=%u", zonename, version), errbuf, &result)) { - safe_delete_array(query); - while ((row = mysql_fetch_row(result))) - { - lengths = mysql_fetch_lengths(result); - Trap* trap = new Trap(); - trap->trap_id = atoi(row[0]); - trap->x = atof(row[1]); - trap->y = atof(row[2]); - trap->z = atof(row[3]); - trap->effect = atoi(row[4]); - trap->effectvalue = atoi(row[5]); - trap->effectvalue2 = atoi(row[6]); - trap->skill = atoi(row[7]); - trap->maxzdiff = atof(row[8]); - trap->radius = atof(row[9]); - trap->chance = atoi(row[10]); - trap->message = row[11]; - trap->respawn_time = atoi(row[12]); - trap->respawn_var = atoi(row[13]); - trap->level = atoi(row[14]); - entity_list.AddTrap(trap); - trap->CreateHiddenTrigger(); - } - mysql_free_result(result); - } - else { - LogFile->write(EQEMuLog::Error, "Error in LoadTraps query '%s': %s", query, errbuf); - safe_delete_array(query); + std::string query = StringFormat("SELECT id, x, y, z, effect, effectvalue, effectvalue2, skill, " + "maxzdiff, radius, chance, message, respawn_time, respawn_var, level " + "FROM traps WHERE zone='%s' AND version=%u", zonename, version); + auto results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in LoadTraps query '%s': %s", query.c_str(), results.ErrorMessage().c_str()); return false; - } + } + + for (auto row = results.begin(); row != results.end(); ++row) { + Trap* trap = new Trap(); + trap->trap_id = atoi(row[0]); + trap->x = atof(row[1]); + trap->y = atof(row[2]); + trap->z = atof(row[3]); + trap->effect = atoi(row[4]); + trap->effectvalue = atoi(row[5]); + trap->effectvalue2 = atoi(row[6]); + trap->skill = atoi(row[7]); + trap->maxzdiff = atof(row[8]); + trap->radius = atof(row[9]); + trap->chance = atoi(row[10]); + trap->message = row[11]; + trap->respawn_time = atoi(row[12]); + trap->respawn_var = atoi(row[13]); + trap->level = atoi(row[14]); + entity_list.AddTrap(trap); + trap->CreateHiddenTrigger(); + } return true; } diff --git a/zone/tribute.cpp b/zone/tribute.cpp index c70afc992..95da9b5a3 100644 --- a/zone/tribute.cpp +++ b/zone/tribute.cpp @@ -378,68 +378,60 @@ void Client::SendGuildTributes() { } bool ZoneDatabase::LoadTributes() { - char errbuf[MYSQL_ERRMSG_SIZE]; - MYSQL_RES *result; - MYSQL_ROW row; - TributeData t; - memset(&t.tiers, 0, sizeof(t.tiers)); - t.tier_count = 0; + TributeData tributeData; + memset(&tributeData.tiers, 0, sizeof(tributeData.tiers)); + tributeData.tier_count = 0; tribute_list.clear(); - const char *query = "SELECT id,name,descr,unknown,isguild FROM tributes"; - if (RunQuery(query, strlen(query), errbuf, &result)) { - int r; - while ((row = mysql_fetch_row(result))) { - r = 0; - uint32 id = atoul(row[r++]); - t.name = row[r++]; - t.description = row[r++]; - t.unknown = strtoul(row[r++], nullptr, 10); - t.is_guild = atol(row[r++])==0?false:true; - - tribute_list[id] = t; - } - mysql_free_result(result); - } else { - LogFile->write(EQEMuLog::Error, "Error in LoadTributes first query '%s': %s", query, errbuf); + const std::string query = "SELECT id, name, descr, unknown, isguild FROM tributes"; + auto results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in LoadTributes first query '%s': %s", query.c_str(), results.ErrorMessage().c_str()); return false; } + for (auto row = results.begin(); row != results.end(); ++row) { + uint32 id = atoul(row[0]); + tributeData.name = row[1]; + tributeData.description = row[2]; + tributeData.unknown = strtoul(row[3], nullptr, 10); + tributeData.is_guild = atol(row[4]) == 0? false: true; - const char *query2 = "SELECT tribute_id,level,cost,item_id FROM tribute_levels ORDER BY tribute_id,level"; - if (RunQuery(query2, strlen(query2), errbuf, &result)) { - int r; - while ((row = mysql_fetch_row(result))) { - r = 0; - uint32 id = atoul(row[r++]); + tribute_list[id] = tributeData; + } - if(tribute_list.count(id) != 1) { - LogFile->write(EQEMuLog::Error, "Error in LoadTributes: unknown tribute %lu in tribute_levels", (unsigned long)id); - continue; - } - - TributeData &cur = tribute_list[id]; - - if(cur.tier_count >= MAX_TRIBUTE_TIERS) { - LogFile->write(EQEMuLog::Error, "Error in LoadTributes: on tribute %lu: more tiers defined than permitted", (unsigned long)id); - continue; - } - - TributeLevel_Struct &s = cur.tiers[cur.tier_count]; - - s.level = atoul(row[r++]); - s.cost = atoul(row[r++]); - s.tribute_item_id = atoul(row[r++]); - cur.tier_count++; - } - mysql_free_result(result); - } else { - LogFile->write(EQEMuLog::Error, "Error in LoadTributes level query '%s': %s", query, errbuf); + const std::string query2 = "SELECT tribute_id, level, cost, item_id FROM tribute_levels ORDER BY tribute_id, level"; + results = QueryDatabase(query2); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in LoadTributes level query '%s': %s", query.c_str(), results.ErrorMessage().c_str()); return false; } + for (auto row = results.begin(); row != results.end(); ++row) { + uint32 id = atoul(row[0]); + + if(tribute_list.count(id) != 1) { + LogFile->write(EQEMuLog::Error, "Error in LoadTributes: unknown tribute %lu in tribute_levels", (unsigned long)id); + continue; + } + + TributeData &cur = tribute_list[id]; + + if(cur.tier_count >= MAX_TRIBUTE_TIERS) { + LogFile->write(EQEMuLog::Error, "Error in LoadTributes: on tribute %lu: more tiers defined than permitted", (unsigned long)id); + continue; + } + + TributeLevel_Struct &s = cur.tiers[cur.tier_count]; + + s.level = atoul(row[1]); + s.cost = atoul(row[2]); + s.tribute_item_id = atoul(row[3]); + cur.tier_count++; + } + return true; } diff --git a/zone/waypoints.cpp b/zone/waypoints.cpp index 8c8a3f218..c3a19a6f3 100644 --- a/zone/waypoints.cpp +++ b/zone/waypoints.cpp @@ -869,97 +869,78 @@ void NPC::AssignWaypoints(int32 grid) { return; } - char errbuf[MYSQL_ERRMSG_SIZE]; - char *query = 0; - MYSQL_RES *result; - MYSQL_ROW row; - - bool GridErr = false, WPErr = false; Waypoints.clear(); + roamer = false; // Retrieve the wander and pause types for this grid - if(database.RunQuery(query,MakeAnyLenString(&query,"SELECT `type`,`type2` FROM `grid` WHERE `id`=%i AND `zoneid`=%i",grid,zone->GetZoneID()),errbuf, &result)) - { - if((row = mysql_fetch_row(result))) - { - if(row[0] != 0) - wandertype = atoi(row[0]); - else - wandertype = 0; - if(row[1] != 0) - pausetype = atoi(row[1]); - else - pausetype = 0; - } - else // No grid record found in this zone for the given ID - GridErr = true; - mysql_free_result(result); + std::string query = StringFormat("SELECT `type`, `type2` FROM `grid` WHERE `id` = %i AND `zoneid` = %i", grid, zone->GetZoneID()); + auto results = database.QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "MySQL Error while trying to assign grid %u to mob %s: %s", grid, name, results.ErrorMessage().c_str()); + return; } - else // DB query error! - { - GridErr = true; - LogFile->write(EQEMuLog::Error, "MySQL Error while trying to assign grid %u to mob %s: %s", grid, name, errbuf); - } - safe_delete_array(query); - if(!GridErr) - { - this->CastToNPC()->SetGrid(grid); // Assign grid number + if (results.RowCount() == 0) + return; - // Retrieve all waypoints for this grid - if(database.RunQuery(query,MakeAnyLenString(&query,"SELECT `x`,`y`,`z`,`pause`,`heading` FROM grid_entries WHERE `gridid`=%i AND `zoneid`=%i ORDER BY `number`",grid,zone->GetZoneID()),errbuf,&result)) - { - roamer = true; - max_wp = -1; // Initialize it; will increment it for each waypoint successfully added to the list + auto row = results.begin(); - while((row = mysql_fetch_row(result))) - { - if(row[0] != 0 && row[1] != 0 && row[2] != 0 && row[3] != 0) - { - wplist newwp; - newwp.index = ++max_wp; - newwp.x = atof(row[0]); - newwp.y = atof(row[1]); - newwp.z = atof(row[2]); + wandertype = atoi(row[0]); + pausetype = atoi(row[1]); - if(zone->HasMap() && RuleB(Map, FixPathingZWhenLoading) ) - { - if(!RuleB(Watermap, CheckWaypointsInWaterWhenLoading) || !zone->HasWaterMap() || - (zone->HasWaterMap() && !zone->watermap->InWater(newwp.x, newwp.y, newwp.z))) - { - Map::Vertex dest(newwp.x, newwp.y, newwp.z); - float newz = zone->zonemap->FindBestZ(dest, nullptr); + this->CastToNPC()->SetGrid(grid); // Assign grid number - if( (newz > -2000) && ABS(newz-dest.z) < RuleR(Map, FixPathingZMaxDeltaLoading)) - newwp.z = newz + 1; - } - } + // Retrieve all waypoints for this grid + query = StringFormat("SELECT `x`,`y`,`z`,`pause`,`heading` " + "FROM grid_entries WHERE `gridid` = %i AND `zoneid` = %i " + "ORDER BY `number`", grid, zone->GetZoneID()); + results = database.QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "MySQL Error while trying to assign waypoints from grid %u to mob %s: %s", grid, name, results.ErrorMessage().c_str()); + return; + } + + roamer = true; + max_wp = 0; // Initialize it; will increment it for each waypoint successfully added to the list + + for (auto row = results.begin(); row != results.end(); ++row, ++max_wp) + { + wplist newwp; + newwp.index = max_wp; + newwp.x = atof(row[0]); + newwp.y = atof(row[1]); + newwp.z = atof(row[2]); + + if(zone->HasMap() && RuleB(Map, FixPathingZWhenLoading) ) + { + if(!RuleB(Watermap, CheckWaypointsInWaterWhenLoading) || !zone->HasWaterMap() || + (zone->HasWaterMap() && !zone->watermap->InWater(newwp.x, newwp.y, newwp.z))) + { + Map::Vertex dest(newwp.x, newwp.y, newwp.z); + + float newz = zone->zonemap->FindBestZ(dest, nullptr); + + if( (newz > -2000) && ABS(newz-dest.z) < RuleR(Map, FixPathingZMaxDeltaLoading)) + newwp.z = newz + 1; + } + } + + newwp.pause = atoi(row[3]); + newwp.heading = atof(row[4]); + Waypoints.push_back(newwp); + } - newwp.pause = atoi(row[3]); - newwp.heading = atof(row[4]); - Waypoints.push_back(newwp); - } - } - mysql_free_result(result); - } - else // DB query error! - { - WPErr = true; - LogFile->write(EQEMuLog::Error, "MySQL Error while trying to assign waypoints from grid %u to mob %s: %s", grid, name, errbuf); - } - safe_delete_array(query); - } // end if (!GridErr) if(Waypoints.size() < 2) { roamer = false; - } else if(!GridErr && !WPErr) { - UpdateWaypoint(0); - SetWaypointPause(); - if (wandertype == 1 || wandertype == 2 || wandertype == 5) - CalculateNewWaypoint(); - } else { - roamer = false; } + + UpdateWaypoint(0); + SetWaypointPause(); + + if (wandertype == 1 || wandertype == 2 || wandertype == 5) + CalculateNewWaypoint(); + } void Mob::SendTo(float new_x, float new_y, float new_z) { @@ -1049,159 +1030,123 @@ int ZoneDatabase::GetHighestGrid(uint32 zoneid) { } uint8 ZoneDatabase::GetGridType2(uint32 grid, uint16 zoneid) { - char *query = 0; - char errbuff[MYSQL_ERRMSG_SIZE]; - MYSQL_RES *result; - MYSQL_ROW row; - int type2 = 0; - if (RunQuery(query, MakeAnyLenString(&query,"SELECT type2 from grid where id = %i and zoneid = %i",grid,zoneid),errbuff,&result)) { - safe_delete_array(query); - if (mysql_num_rows(result) == 1) { - row = mysql_fetch_row(result); - type2 = atoi( row[0] ); - } - mysql_free_result(result); - } else { - LogFile->write(EQEMuLog::Error, "Error in GetGridType2 query '%s': %s", query, errbuff); - safe_delete_array(query); - } - return(type2); + int type2 = 0; + std::string query = StringFormat("SELECT type2 FROM grid WHERE id = %i AND zoneid = %i", grid, zoneid); + auto results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in GetGridType2 query '%s': %s", query.c_str(), results.ErrorMessage().c_str()); + return 0; + } + + if (results.RowCount() != 1) + return 0; + + auto row = results.begin(); + + return atoi(row[0]); } bool ZoneDatabase::GetWaypoints(uint32 grid, uint16 zoneid, uint32 num, wplist* wp) { - char *query = 0; - char errbuff[MYSQL_ERRMSG_SIZE]; - MYSQL_RES *result; - MYSQL_ROW row; - if (RunQuery(query, MakeAnyLenString(&query,"SELECT x, y, z, pause, heading from grid_entries where gridid = %i and number = %i and zoneid = %i",grid,num,zoneid),errbuff,&result)) { - safe_delete_array(query); - if (mysql_num_rows(result) == 1) { - row = mysql_fetch_row(result); - if ( wp ) { - wp->x = atof( row[0] ); - wp->y = atof( row[1] ); - wp->z = atof( row[2] ); - wp->pause = atoi( row[3] ); - wp->heading = atof( row[4] ); - } - mysql_free_result(result); - return true; - } - mysql_free_result(result); - } - else { - LogFile->write(EQEMuLog::Error, "Error in GetWaypoints query '%s': %s", query, errbuff); - safe_delete_array(query); - } - return false; + + if (wp == nullptr) + return false; + + std::string query = StringFormat("SELECT x, y, z, pause, heading FROM grid_entries " + "WHERE gridid = %i AND number = %i AND zoneid = %i", grid, num, zoneid); + auto results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in GetWaypoints query '%s': %s", query.c_str(), results.ErrorMessage().c_str()); + return false; + } + + if (results.RowCount() != 1) + return false; + + auto row = results.begin(); + + wp->x = atof(row[0]); + wp->y = atof(row[1]); + wp->z = atof(row[2]); + wp->pause = atoi(row[3]); + wp->heading = atof(row[4]); + + return true; } void ZoneDatabase::AssignGrid(Client *client, float x, float y, uint32 grid) { - char *query = 0; - char errbuf[MYSQL_ERRMSG_SIZE]; - MYSQL_RES *result; - MYSQL_ROW row; int matches = 0, fuzzy = 0, spawn2id = 0; - uint32 affected_rows; float dbx = 0, dby = 0; // looks like most of the stuff in spawn2 is straight integers // so let's try that first - if(!RunQuery( - query, - MakeAnyLenString( - &query, - "SELECT id,x,y FROM spawn2 WHERE zone='%s' AND x=%i AND y=%i", - zone->GetShortName(), (int)x, (int)y - ), - errbuf, - &result - )) { - LogFile->write(EQEMuLog::Error, "Error querying spawn2 '%s': '%s'", query, errbuf); - return; + std::string query = StringFormat("SELECT id, x, y FROM spawn2 WHERE zone = '%s' AND x = %i AND y = %i", + zone->GetShortName(), (int)x, (int)y); + auto results = QueryDatabase(query); + if(!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error querying spawn2 '%s': '%s'", query.c_str(), results.ErrorMessage().c_str()); + return; } - safe_delete_array(query); // how much it's allowed to be off by #define _GASSIGN_TOLERANCE 1.0 - if(!(matches = mysql_num_rows(result))) // try a fuzzy match if that didn't find it + if(results.RowCount() == 0) // try a fuzzy match if that didn't find it { - mysql_free_result(result); - if(!RunQuery( - query, - MakeAnyLenString( - &query, - "SELECT id,x,y FROM spawn2 WHERE zone='%s' AND " - "ABS( ABS(x) - ABS(%f) ) < %f AND " - "ABS( ABS(y) - ABS(%f) ) < %f", - zone->GetShortName(), x, _GASSIGN_TOLERANCE, y, _GASSIGN_TOLERANCE - ), - errbuf, - &result - )) { - LogFile->write(EQEMuLog::Error, "Error querying fuzzy spawn2 '%s': '%s'", query, errbuf); + query = StringFormat("SELECT id,x,y FROM spawn2 WHERE zone='%s' AND " + "ABS( ABS(x) - ABS(%f) ) < %f AND " + "ABS( ABS(y) - ABS(%f) ) < %f", + zone->GetShortName(), x, _GASSIGN_TOLERANCE, y, _GASSIGN_TOLERANCE); + results = QueryDatabase(query); + if(!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error querying fuzzy spawn2 '%s': '%s'", query.c_str(), results.ErrorMessage().c_str()); return; } - safe_delete_array(query); + fuzzy = 1; - if(!(matches = mysql_num_rows(result))) - mysql_free_result(result); + matches = results.RowCount(); } - if(matches) + + if (matches == 0) { + client->Message(0, "ERROR: Unable to assign grid - can't find it in spawn2"); + return; + } + + if(matches == 1) { - if(matches > 1) - { - client->Message(0, "ERROR: Unable to assign grid - multiple spawn2 rows match"); - mysql_free_result(result); - } - else - { - row = mysql_fetch_row(result); - spawn2id = atoi(row[0]); - dbx = atof(row[1]); - dby = atof(row[2]); - if(!RunQuery( - query, - MakeAnyLenString( - &query, - "UPDATE spawn2 SET pathgrid = %d WHERE id = %d", grid, spawn2id - ), - errbuf, - &result, - &affected_rows - )) { - LogFile->write(EQEMuLog::Error, "Error updating spawn2 '%s': '%s'", query, errbuf); - return; - } - if(affected_rows == 1) - { - if(client) client->LogSQL(query); - if(fuzzy) - { - float difference; - difference = sqrtf(pow(fabs(x-dbx),2) + pow(fabs(y-dby),2)); - client->Message(0, - "Grid assign: spawn2 id = %d updated - fuzzy match: deviation %f", - spawn2id, difference - ); - } - else - { - client->Message(0, "Grid assign: spawn2 id = %d updated - exact match", spawn2id); - } - } - else - { - client->Message(0, "ERROR: found spawn2 id %d but the update query failed", spawn2id); - } - } + client->Message(0, "ERROR: Unable to assign grid - multiple spawn2 rows match"); + return; } - else + + auto row = results.begin(); + + spawn2id = atoi(row[0]); + dbx = atof(row[1]); + dby = atof(row[2]); + + query = StringFormat("UPDATE spawn2 SET pathgrid = %d WHERE id = %d", grid, spawn2id); + results = QueryDatabase(query); + if (!results.Success()) { - client->Message(0, "ERROR: Unable to assign grid - can't find it in spawn2"); - } + LogFile->write(EQEMuLog::Error, "Error updating spawn2 '%s': '%s'", query.c_str(), results.ErrorMessage().c_str()); + return; + } + + if (results.RowsAffected() != 1) { + client->Message(0, "ERROR: found spawn2 id %d but the update query failed", spawn2id); + return; + } + + if(client) + client->LogSQL(query.c_str()); + + if(!fuzzy) { + client->Message(0, "Grid assign: spawn2 id = %d updated - exact match", spawn2id); + return; + } + + float difference = sqrtf(pow(fabs(x - dbx) , 2) + pow(fabs(y - dby), 2)); + client->Message(0, "Grid assign: spawn2 id = %d updated - fuzzy match: deviation %f", spawn2id, difference); } /****************** @@ -1211,53 +1156,57 @@ void ZoneDatabase::AssignGrid(Client *client, float x, float y, uint32 grid) * type,type2: The type and type2 values for the grid being created (ignored if grid is being deleted) * zoneid: The ID number of the zone the grid is being created/deleted in */ +void ZoneDatabase::ModifyGrid(Client *client, bool remove, uint32 id, uint8 type, uint8 type2, uint16 zoneid) { -void ZoneDatabase::ModifyGrid(Client *c, bool remove, uint32 id, uint8 type, uint8 type2, uint16 zoneid) { - char *query = 0; - char errbuf[MYSQL_ERRMSG_SIZE]; if (!remove) { - if(!RunQuery(query, MakeAnyLenString(&query,"INSERT INTO grid(id,zoneid,type,type2) VALUES(%i,%i,%i,%i)",id,zoneid,type,type2), errbuf)) { - LogFile->write(EQEMuLog::Error, "Error creating grid entry '%s': '%s'", query, errbuf); - } else { - if(c) c->LogSQL(query); - } - safe_delete_array(query); + std::string query = StringFormat("INSERT INTO grid(id, zoneid, type, type2) " + "VALUES (%i, %i, %i, %i)", id, zoneid, type, type2); + auto results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error creating grid entry '%s': '%s'", query.c_str(), results.ErrorMessage().c_str()); + return; + } + + if(client) + client->LogSQL(query.c_str()); + + return; } - else - { - if(!RunQuery(query, MakeAnyLenString(&query,"DELETE FROM grid where id=%i",id), errbuf)) { - LogFile->write(EQEMuLog::Error, "Error deleting grid '%s': '%s'", query, errbuf); - } else { - if(c) c->LogSQL(query); - } - safe_delete_array(query); - query = 0; - if(!RunQuery(query, MakeAnyLenString(&query,"DELETE FROM grid_entries WHERE zoneid=%i AND gridid=%i",zoneid,id), errbuf)) { - LogFile->write(EQEMuLog::Error, "Error deleting grid entries '%s': '%s'", query, errbuf); - } else { - if(c) c->LogSQL(query); - } - safe_delete_array(query); - } -} /*** END ZoneDatabase::ModifyGrid() ***/ + + std::string query = StringFormat("DELETE FROM grid where id=%i", id); + auto results = QueryDatabase(query); + if (!results.Success()) + LogFile->write(EQEMuLog::Error, "Error deleting grid '%s': '%s'", query.c_str(), results.ErrorMessage().c_str()); + else if(client) + client->LogSQL(query.c_str()); + + query = StringFormat("DELETE FROM grid_entries WHERE zoneid = %i AND gridid = %i", zoneid, id); + results = QueryDatabase(query); + if(!results.Success()) + LogFile->write(EQEMuLog::Error, "Error deleting grid entries '%s': '%s'", query.c_str(), results.ErrorMessage().c_str()); + else if(client) + client->LogSQL(query.c_str()); + +} /************************************** * AddWP - Adds a new waypoint to a specific grid for a specific zone. */ - -void ZoneDatabase::AddWP(Client *c, uint32 gridid, uint32 wpnum, float xpos, float ypos, float zpos, uint32 pause, uint16 zoneid, float heading) +void ZoneDatabase::AddWP(Client *client, uint32 gridid, uint32 wpnum, float xpos, float ypos, float zpos, uint32 pause, uint16 zoneid, float heading) { - char *query = 0; - char errbuf[MYSQL_ERRMSG_SIZE]; - - if(!RunQuery(query,MakeAnyLenString(&query,"INSERT INTO grid_entries (gridid,zoneid,`number`,x,y,z,pause,heading) values (%i,%i,%i,%f,%f,%f,%i,%f)",gridid,zoneid,wpnum,xpos,ypos,zpos,pause,heading), errbuf)) { - LogFile->write(EQEMuLog::Error, "Error adding waypoint '%s': '%s'", query, errbuf); - } else { - if(c) c->LogSQL(query); + std::string query = StringFormat("INSERT INTO grid_entries (gridid, zoneid, `number`, x, y, z, pause, heading) " + "VALUES (%i, %i, %i, %f, %f, %f, %i, %f)", + gridid, zoneid, wpnum, xpos, ypos, zpos, pause, heading); + auto results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error adding waypoint '%s': '%s'", query.c_str(), results.ErrorMessage().c_str()); + return; } - safe_delete_array(query); -} /*** END ZoneDatabase::AddWP() ***/ + + if(client) + client->LogSQL(query.c_str()); +} /********** @@ -1270,19 +1219,20 @@ void ZoneDatabase::AddWP(Client *c, uint32 gridid, uint32 wpnum, float xpos, flo * wp_num: The number of the waypoint being deleted * zoneid: The ID number of the zone that contains the waypoint being deleted */ - -void ZoneDatabase::DeleteWaypoint(Client *c, uint32 grid_num, uint32 wp_num, uint16 zoneid) +void ZoneDatabase::DeleteWaypoint(Client *client, uint32 grid_num, uint32 wp_num, uint16 zoneid) { - char *query=0; - char errbuf[MYSQL_ERRMSG_SIZE]; - - if(!RunQuery(query, MakeAnyLenString(&query,"DELETE FROM grid_entries where gridid=%i and zoneid=%i and `number`=%i",grid_num,zoneid,wp_num), errbuf)) { - LogFile->write(EQEMuLog::Error, "Error deleting waypoint '%s': '%s'", query, errbuf); - } else { - if(c) c->LogSQL(query); + std::string query = StringFormat("DELETE FROM grid_entries WHERE " + "gridid = %i AND zoneid = %i AND `number` = %i", + grid_num, zoneid, wp_num); + auto results = QueryDatabase(query); + if(!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error deleting waypoint '%s': '%s'", query.c_str(), results.ErrorMessage().c_str()); + return; } - safe_delete_array(query); -} /*** END ZoneDatabase::DeleteWaypoint() ***/ + + if(client) + client->LogSQL(query.c_str()); +} /****************** @@ -1292,139 +1242,112 @@ void ZoneDatabase::DeleteWaypoint(Client *c, uint32 grid_num, uint32 wp_num, uin * Returns 0 if the function didn't have to create a new grid. If the function had to create a new grid for the spawn, then the ID of * the created grid is returned. */ +uint32 ZoneDatabase::AddWPForSpawn(Client *client, uint32 spawn2id, float xpos, float ypos, float zpos, uint32 pause, int type1, int type2, uint16 zoneid, float heading) { -uint32 ZoneDatabase::AddWPForSpawn(Client *c, uint32 spawn2id, float xpos, float ypos, float zpos, uint32 pause, int type1, int type2, uint16 zoneid, float heading) { - char *query = 0; - uint32 grid_num, // The grid number the spawn is assigned to (if spawn has no grid, will be the grid number we end up creating) - next_wp_num; // The waypoint number we should be assigning to the new waypoint - bool CreatedNewGrid; // Did we create a new grid in this function? - MYSQL_RES *result; - MYSQL_ROW row; - char errbuf[MYSQL_ERRMSG_SIZE]; + uint32 grid_num; // The grid number the spawn is assigned to (if spawn has no grid, will be the grid number we end up creating) + uint32 next_wp_num; // The waypoint number we should be assigning to the new waypoint + bool createdNewGrid; // Did we create a new grid in this function? // See what grid number our spawn is assigned - if(RunQuery(query, MakeAnyLenString(&query,"SELECT pathgrid FROM spawn2 WHERE id=%i",spawn2id),errbuf,&result)) - { - safe_delete_array(query); - if(mysql_num_rows(result) > 0) - { - row = mysql_fetch_row(result); - grid_num = atoi(row[0]); - } - else // This spawn ID was not found in the `spawn2` table - return 0; - - mysql_free_result(result); - } - else { // Query error - LogFile->write(EQEMuLog::Error, "Error setting pathgrid '%s': '%s'", query, errbuf); + std::string query = StringFormat("SELECT pathgrid FROM spawn2 WHERE id = %i", spawn2id); + auto results = QueryDatabase(query); + if (!results.Success()) { + // Query error + LogFile->write(EQEMuLog::Error, "Error setting pathgrid '%s': '%s'", query.c_str(), results.ErrorMessage().c_str()); return 0; } - if (grid_num == 0) // Our spawn doesn't have a grid assigned to it -- we need to create a new grid and assign it to the spawn - { - CreatedNewGrid = true; - if((grid_num = GetFreeGrid(zoneid)) == 0) // There are no grids for the current zone -- create Grid #1 - grid_num = 1; + if (results.RowCount() == 0) + return 0; - if(!RunQuery(query, MakeAnyLenString(&query,"insert into grid set id='%i',zoneid= %i, type='%i', type2='%i'",grid_num,zoneid,type1,type2), errbuf)) { - LogFile->write(EQEMuLog::Error, "Error adding grid '%s': '%s'", query, errbuf); - } else { - if(c) c->LogSQL(query); - } - safe_delete_array(query); + auto row = results.begin(); + grid_num = atoi(row[0]); - query = 0; - if(!RunQuery(query, MakeAnyLenString(&query,"update spawn2 set pathgrid='%i' where id='%i'",grid_num,spawn2id), errbuf)) { - LogFile->write(EQEMuLog::Error, "Error updating spawn2 pathing '%s': '%s'", query, errbuf); - } else { - if(c) c->LogSQL(query); - } - safe_delete_array(query); + if (grid_num == 0) + { // Our spawn doesn't have a grid assigned to it -- we need to create a new grid and assign it to the spawn + createdNewGrid = true; + grid_num = GetFreeGrid(zoneid); + if(grid_num == 0) // There are no grids for the current zone -- create Grid #1 + grid_num = 1; + + query = StringFormat("INSERT INTO grid SET id = '%i', zoneid = %i, type ='%i', type2 = '%i'", + grid_num, zoneid, type1, type2); + results = QueryDatabase(query); + if(!results.Success()) + LogFile->write(EQEMuLog::Error, "Error adding grid '%s': '%s'", query.c_str(), results.ErrorMessage().c_str()); + else if(client) + client->LogSQL(query.c_str()); + + query = StringFormat("UPDATE spawn2 SET pathgrid = '%i' WHERE id = '%i'", grid_num, spawn2id); + results = QueryDatabase(query); + if(!results.Success()) + LogFile->write(EQEMuLog::Error, "Error updating spawn2 pathing '%s': '%s'", query.c_str(), results.ErrorMessage().c_str()); + else if(client) + client->LogSQL(query.c_str()); } else // NPC had a grid assigned to it - CreatedNewGrid = false; - + createdNewGrid = false; // Find out what the next waypoint is for this grid - query = 0; - if(RunQuery(query, MakeAnyLenString(&query,"SELECT max(`number`) FROM grid_entries WHERE zoneid='%i' AND gridid='%i'",zoneid,grid_num),errbuf,&result)) - { - safe_delete_array(query); - row = mysql_fetch_row(result); - if(row[0] != 0) - next_wp_num = atoi(row[0]) + 1; - else // No waypoints in this grid yet - next_wp_num = 1; + query = StringFormat("SELECT max(`number`) FROM grid_entries WHERE zoneid = '%i' AND gridid = '%i'", zoneid, grid_num); - mysql_free_result(result); - } - else { // Query error - LogFile->write(EQEMuLog::Error, "Error getting next waypoint id '%s': '%s'", query, errbuf); + if(!results.Success()) { // Query error + LogFile->write(EQEMuLog::Error, "Error getting next waypoint id '%s': '%s'", query.c_str(), results.ErrorMessage().c_str()); return 0; } - query = 0; - if(!RunQuery(query, MakeAnyLenString(&query,"INSERT INTO grid_entries(gridid,zoneid,`number`,x,y,z,pause,heading) VALUES (%i,%i,%i,%f,%f,%f,%i,%f)",grid_num,zoneid,next_wp_num,xpos,ypos,zpos,pause,heading), errbuf)) { - LogFile->write(EQEMuLog::Error, "Error adding grid entry '%s': '%s'", query, errbuf); - } else { - if(c) c->LogSQL(query); - } - safe_delete_array(query); + row = results.begin(); + if(row[0] != 0) + next_wp_num = atoi(row[0]) + 1; + else // No waypoints in this grid yet + next_wp_num = 1; - if(CreatedNewGrid) - return grid_num; - - return 0; -} /*** END ZoneDatabase::AddWPForSpawn() ***/ + query = StringFormat("INSERT INTO grid_entries(gridid, zoneid, `number`, x, y, z, pause, heading) " + "VALUES (%i, %i, %i, %f, %f, %f, %i, %f)", + grid_num, zoneid, next_wp_num, xpos, ypos, zpos, pause, heading); + results = QueryDatabase(query); + if(!results.Success()) + LogFile->write(EQEMuLog::Error, "Error adding grid entry '%s': '%s'", query.c_str(), results.ErrorMessage().c_str()); + else if(client) + client->LogSQL(query.c_str()); + return createdNewGrid? grid_num: 0; +} uint32 ZoneDatabase::GetFreeGrid(uint16 zoneid) { - char *query = 0; - char errbuf[MYSQL_ERRMSG_SIZE]; - MYSQL_RES *result; - MYSQL_ROW row; - if (RunQuery(query, MakeAnyLenString(&query,"SELECT max(id) from grid where zoneid = %i",zoneid),errbuf,&result)) { - safe_delete_array(query); - if (mysql_num_rows(result) == 1) { - row = mysql_fetch_row(result); - uint32 tmp=0; - if (row[0]) - tmp = atoi(row[0]); - mysql_free_result(result); - tmp++; - return tmp; - } - mysql_free_result(result); + + std::string query = StringFormat("SELECT max(id) FROM grid WHERE zoneid = %i", zoneid); + auto results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in GetFreeGrid query '%s': %s", query.c_str(), results.ErrorMessage().c_str()); + return 0; } - else { - LogFile->write(EQEMuLog::Error, "Error in GetFreeGrid query '%s': %s", query, errbuf); - safe_delete_array(query); - } - return 0; + + if (results.RowCount() != 1) + return 0; + + auto row = results.begin(); + uint32 freeGridID = 1; + freeGridID = atoi(row[0]) + 1; + + return freeGridID; } int ZoneDatabase::GetHighestWaypoint(uint32 zoneid, uint32 gridid) { - char *query = 0; - char errbuff[MYSQL_ERRMSG_SIZE]; - MYSQL_RES *result; - MYSQL_ROW row; - int res = 0; - if (RunQuery(query, MakeAnyLenString(&query, - "SELECT COALESCE(MAX(number), 0) FROM grid_entries WHERE zoneid = %i AND gridid = %i", - zoneid, gridid),errbuff,&result)) { - safe_delete_array(query); - if (mysql_num_rows(result) == 1) { - row = mysql_fetch_row(result); - res = atoi( row[0] ); - } - mysql_free_result(result); - } else { - LogFile->write(EQEMuLog::Error, "Error in GetHighestWaypoint query '%s': %s", query, errbuff); - safe_delete_array(query); + + std::string query = StringFormat("SELECT COALESCE(MAX(number), 0) FROM grid_entries " + "WHERE zoneid = %i AND gridid = %i", zoneid, gridid); + auto results = QueryDatabase(query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in GetHighestWaypoint query '%s': %s", query.c_str(), results.ErrorMessage().c_str()); + return 0; } - return(res); + if (results.RowCount() != 1) + return 0; + + auto row = results.begin(); + return atoi(row[0]); } void NPC::SaveGuardSpotCharm() diff --git a/zone/zone.cpp b/zone/zone.cpp index a3026425c..8f4b211d8 100644 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -718,11 +718,24 @@ void Zone::DBAWComplete(uint8 workpt_b1, DBAsyncWork* dbaw) { } } +bool Zone::IsLoaded() { + return ZoneLoaded; +} + void Zone::Shutdown(bool quite) { if (!ZoneLoaded) return; + std::list mob_list; + entity_list.GetMobList(mob_list); + std::list::iterator mob_itr = mob_list.begin(); + while (mob_itr != mob_list.end()) { + Mob* mob_inst = *mob_itr; + mob_inst->AI_Stop(); + ++mob_itr; + } + std::map::iterator itr; while(zone->npctable.size()) { itr=zone->npctable.begin(); @@ -1678,45 +1691,41 @@ ZonePoint* Zone::GetClosestZonePointWithoutZone(float x, float y, float z, Clien bool ZoneDatabase::LoadStaticZonePoints(LinkedList* zone_point_list, const char* zonename, uint32 version) { - char errbuf[MYSQL_ERRMSG_SIZE]; - char *query = 0; - MYSQL_RES *result; - MYSQL_ROW row; + zone_point_list->Clear(); zone->numzonepoints = 0; - MakeAnyLenString(&query, "SELECT x, y, z, target_x, target_y, " - "target_z, target_zone_id, heading, target_heading, number, " - "target_instance, client_version_mask FROM zone_points " - "WHERE zone='%s' AND (version=%i OR version=-1) order by number", zonename, version); - if (RunQuery(query, strlen(query), errbuf, &result)) - { - safe_delete_array(query); - while((row = mysql_fetch_row(result))) - { - ZonePoint* zp = new ZonePoint; - zp->x = atof(row[0]); - zp->y = atof(row[1]); - zp->z = atof(row[2]); - zp->target_x = atof(row[3]); - zp->target_y = atof(row[4]); - zp->target_z = atof(row[5]); - zp->target_zone_id = atoi(row[6]); - zp->heading = atof(row[7]); - zp->target_heading = atof(row[8]); - zp->number = atoi(row[9]); - zp->target_zone_instance = atoi(row[10]); - zp->client_version_mask = (uint32)strtoul(row[11], nullptr, 0); - zone_point_list->Insert(zp); - zone->numzonepoints++; - } - mysql_free_result(result); - } - else - { - std::cerr << "Error1 in LoadStaticZonePoints query '" << query << "' " << errbuf << std::endl; - safe_delete_array(query); + std::string query = StringFormat("SELECT x, y, z, target_x, target_y, " + "target_z, target_zone_id, heading, target_heading, " + "number, target_instance, client_version_mask " + "FROM zone_points WHERE zone='%s' AND (version=%i OR version=-1) " + "ORDER BY number", zonename, version); + auto results = QueryDatabase(query); + if (!results.Success()) { + std::cerr << "Error1 in LoadStaticZonePoints query '" << query << "' " << results.ErrorMessage() << std::endl; return false; } + + for (auto row = results.begin(); row != results.end(); ++row) { + ZonePoint* zp = new ZonePoint; + + zp->x = atof(row[0]); + zp->y = atof(row[1]); + zp->z = atof(row[2]); + zp->target_x = atof(row[3]); + zp->target_y = atof(row[4]); + zp->target_z = atof(row[5]); + zp->target_zone_id = atoi(row[6]); + zp->heading = atof(row[7]); + zp->target_heading = atof(row[8]); + zp->number = atoi(row[9]); + zp->target_zone_instance = atoi(row[10]); + zp->client_version_mask = (uint32)strtoul(row[11], nullptr, 0); + + zone_point_list->Insert(zp); + + zone->numzonepoints++; + } + return true; } diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 92bf3cc22..787e3a94e 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -3226,3 +3226,11 @@ bool ZoneDatabase::GetFactionIdsForNPC(uint32 nfl_id, std::listGetZoneID(), this->GetInstanceID()); + QServ->PlayerLogEvent(Player_Log_Zoning, this->CharacterID(), event_desc); + } + + /* Dont clear aggro until the zone is successful */ entity_list.RemoveFromHateLists(this); if(this->GetPet()) entity_list.RemoveFromHateLists(this->GetPet()); - LogFile->write(EQEMuLog::Status, "Zoning '%s' to: %s (%i) - (%i) x=%f, y=%f, z=%f", - m_pp.name, database.GetZoneName(zone_id), zone_id, instance_id, - dest_x, dest_y, dest_z); + LogFile->write(EQEMuLog::Status, "Zoning '%s' to: %s (%i) - (%i) x=%f, y=%f, z=%f", m_pp.name, database.GetZoneName(zone_id), zone_id, instance_id, dest_x, dest_y, dest_z); //set the player's coordinates in the new zone so they have them //when they zone into it