Merge branch 'master' of github.com:EQEmu/Server

This commit is contained in:
KimLS 2014-08-28 03:18:02 -07:00
commit 467afc86af
23 changed files with 2064 additions and 2311 deletions

View File

@ -1,5 +1,11 @@
EQEMu Changelog (Started on Sept 24, 2003 15:50) 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 == == 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. 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...) Note: Perl and Lua quests tested to work..please post any aberrant behavior. (I finally set my spell-check to US English...)
@ -49,6 +55,16 @@ Akkadius: Spawn related logging cleanup
Akkadius: General code cleanup Akkadius: General code cleanup
Akkadius: More to come for QueryServ 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 == == 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. 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 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

View File

@ -654,6 +654,99 @@ int16 Inventory::FindFreeSlot(bool for_bag, bool try_cursor, uint8 min_size, boo
return INVALID_INDEX; 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 // Opposite of below: Get parent bag slot_id from a slot inside of bag
int16 Inventory::CalcSlotId(int16 slot_id) { int16 Inventory::CalcSlotId(int16 slot_id) {
int16 parent_slot_id = INVALID_INDEX; int16 parent_slot_id = INVALID_INDEX;

View File

@ -172,6 +172,7 @@ public:
// Locate an available inventory slot // Locate an available inventory slot
int16 FindFreeSlot(bool for_bag, bool try_cursor, uint8 min_size = 0, bool is_arrow = false); 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 // Calculate slot_id for an item within a bag
static int16 CalcSlotId(int16 slot_id); // Calc parent bag's slot_id static int16 CalcSlotId(int16 slot_id); // Calc parent bag's slot_id

View File

@ -40,7 +40,7 @@ public:
MySQLRequestResult& operator=(MySQLRequestResult&& other); MySQLRequestResult& operator=(MySQLRequestResult&& other);
bool Success() const { return m_Success;} 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 ErrorNumber() const {return m_ErrorNumber;}
uint32 RowsAffected() const {return m_RowsAffected;} uint32 RowsAffected() const {return m_RowsAffected;}
uint32 RowCount() const {return m_RowCount;} uint32 RowCount() const {return m_RowCount;}

View File

@ -1118,6 +1118,7 @@ struct QSPlayerLogTrade_Struct {
uint32 char2_id; uint32 char2_id;
MoneyUpdate_Struct char2_money; MoneyUpdate_Struct char2_money;
uint16 char2_count; uint16 char2_count;
uint16 _detail_count;
QSTradeItems_Struct items[0]; QSTradeItems_Struct items[0];
}; };
@ -1141,6 +1142,7 @@ struct QSPlayerLogHandin_Struct {
uint32 npc_id; uint32 npc_id;
MoneyUpdate_Struct npc_money; MoneyUpdate_Struct npc_money;
uint16 npc_count; uint16 npc_count;
uint16 _detail_count;
QSHandinItems_Struct items[0]; QSHandinItems_Struct items[0];
}; };

View File

@ -119,7 +119,7 @@ void Database::AddSpeech(const char* from, const char* to, const char* message,
safe_delete_array(S3); 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 errbuf[MYSQL_ERRMSG_SIZE];
char* query = 0; char* query = 0;
@ -134,8 +134,8 @@ void Database::LogPlayerTrade(QSPlayerLogTrade_Struct* QS, uint32 Items) {
_log(QUERYSERV__ERROR, "%s", query); _log(QUERYSERV__ERROR, "%s", query);
} }
if(Items > 0) { if(DetailCount > 0) {
for(int i = 0; i < Items; i++) { for(int i = 0; i < DetailCount; i++) {
if(!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO `qs_player_trade_record_entries` SET `event_id`='%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', " "`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'", "`charges`='%i', `aug_1`='%i', `aug_2`='%i', `aug_3`='%i', `aug_4`='%i', `aug_5`='%i'",
@ -149,7 +149,7 @@ void Database::LogPlayerTrade(QSPlayerLogTrade_Struct* QS, uint32 Items) {
} }
} }
void Database::LogPlayerHandin(QSPlayerLogHandin_Struct* QS, uint32 Items) { void Database::LogPlayerHandin(QSPlayerLogHandin_Struct* QS, uint32 DetailCount) {
char errbuf[MYSQL_ERRMSG_SIZE]; char errbuf[MYSQL_ERRMSG_SIZE];
char* query = 0; char* query = 0;
uint32 lastid = 0; uint32 lastid = 0;
@ -163,8 +163,8 @@ void Database::LogPlayerHandin(QSPlayerLogHandin_Struct* QS, uint32 Items) {
_log(QUERYSERV__ERROR, "%s", query); _log(QUERYSERV__ERROR, "%s", query);
} }
if(Items > 0) { if(DetailCount > 0) {
for(int i = 0; i < Items; i++) { for(int i = 0; i < DetailCount; i++) {
if(!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO `qs_player_handin_record_entries` SET `event_id`='%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', " "`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'", "`aug_1`='%i', `aug_2`='%i', `aug_3`='%i', `aug_4`='%i', `aug_5`='%i'",

View File

@ -43,8 +43,8 @@ public:
~Database(); ~Database();
void AddSpeech(const char* from, const char* to, const char* message, uint16 minstatus, uint32 guilddbid, uint8 type); 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 LogPlayerTrade(QSPlayerLogTrade_Struct* QS, uint32 DetailCount);
void LogPlayerHandin(QSPlayerLogHandin_Struct* QS, uint32 Items); void LogPlayerHandin(QSPlayerLogHandin_Struct* QS, uint32 DetailCount);
void LogPlayerNPCKill(QSPlayerLogNPCKill_Struct* QS, uint32 Members); void LogPlayerNPCKill(QSPlayerLogNPCKill_Struct* QS, uint32 Members);
void LogPlayerDelete(QSPlayerLogDelete_Struct* QS, uint32 Items); void LogPlayerDelete(QSPlayerLogDelete_Struct* QS, uint32 Items);
void LogPlayerMove(QSPlayerLogMove_Struct* QS, uint32 Items); void LogPlayerMove(QSPlayerLogMove_Struct* QS, uint32 Items);

View File

@ -80,14 +80,12 @@ void WorldServer::Process()
} }
case ServerOP_QSPlayerLogTrades: { case ServerOP_QSPlayerLogTrades: {
QSPlayerLogTrade_Struct *QS = (QSPlayerLogTrade_Struct*)pack->pBuffer; QSPlayerLogTrade_Struct *QS = (QSPlayerLogTrade_Struct*)pack->pBuffer;
uint32 Items = QS->char1_count + QS->char2_count; database.LogPlayerTrade(QS, QS->_detail_count);
database.LogPlayerTrade(QS, Items);
break; break;
} }
case ServerOP_QSPlayerLogHandins: { case ServerOP_QSPlayerLogHandins: {
QSPlayerLogHandin_Struct *QS = (QSPlayerLogHandin_Struct*)pack->pBuffer; QSPlayerLogHandin_Struct *QS = (QSPlayerLogHandin_Struct*)pack->pBuffer;
uint32 Items = QS->char_count + QS->npc_count; database.LogPlayerHandin(QS, QS->_detail_count);
database.LogPlayerHandin(QS, Items);
break; break;
} }
case ServerOP_QSPlayerLogNPCKills: { case ServerOP_QSPlayerLogNPCKills: {

View File

@ -102,208 +102,143 @@ Database::~Database()
{ {
} }
void Database::GetAccountStatus(Client *c) { void Database::GetAccountStatus(Client *client) {
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);
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; 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"); _log(UCS__ERROR, "Error in GetAccountStatus");
mysql_free_result(result);
return; return;
} }
row = mysql_fetch_row(result); auto row = results.begin();
c->SetAccountStatus(atoi(row[0])); client->SetAccountStatus(atoi(row[0]));
c->SetHideMe(atoi(row[1]) != 0); client->SetHideMe(atoi(row[1]) != 0);
c->SetKarma(atoi(row[2])); client->SetKarma(atoi(row[2]));
c->SetRevoked((atoi(row[3])==1?true:false)); 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(); client->ClearCharacters();
std::string query = StringFormat("SELECT `id`, `account_id`, `level` "
if (!RunQuery(query,MakeAnyLenString(&query, "select `id`, `account_id`, `level` from `character_` where `name`='%s' limit 1", "FROM `character_` WHERE `name` = '%s' LIMIT 1",
CharacterName),errbuf,&result)) characterName);
{ auto results = QueryDatabase(query);
_log(UCS__ERROR, "FindAccount query failed: %s", query); if (!results.Success()) {
safe_delete_array(query); _log(UCS__ERROR, "FindAccount query failed: %s", query.c_str());
return -1; return -1;
} }
safe_delete_array(query);
if (mysql_num_rows(result) != 1) if (results.RowCount() != 1) {
{
_log(UCS__ERROR, "Bad result from query"); _log(UCS__ERROR, "Bad result from query");
mysql_free_result(result);
return -1; return -1;
} }
row = mysql_fetch_row(result); auto row = results.begin();
c->AddCharacter(atoi(row[0]), CharacterName, atoi(row[2])); client->AddCharacter(atoi(row[0]), characterName, atoi(row[2]));
int AccountID = atoi(row[1]);
mysql_free_result(result); int accountID = atoi(row[1]);
_log(UCS__TRACE, "Account ID for %s is %i", CharacterName, AccountID);
if (!RunQuery(query,MakeAnyLenString(&query, "select `id`, `name`, `level` from `character_` where `account_id`=%i and `name` !='%s'", _log(UCS__TRACE, "Account ID for %s is %i", characterName, accountID);
AccountID, CharacterName),errbuf,&result))
{
safe_delete_array(query);
return AccountID;
}
safe_delete_array(query);
for(unsigned int i = 0; i < mysql_num_rows(result); i++) query = StringFormat("SELECT `id`, `name`, `level` FROM `character_` "
{ "WHERE `account_id` = %i AND `name` != '%s'",
row = mysql_fetch_row(result); accountID, characterName);
c->AddCharacter(atoi(row[0]), row[1], atoi(row[2])); results = QueryDatabase(query);
} if (!results.Success())
mysql_free_result(result); return accountID;
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) { 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);
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; return false;
} }
safe_delete_array(query); auto row = results.begin();
row = mysql_fetch_row(result);
// The key is the client's IP address (expressed as 8 hex digits) and an 8 hex digit random string generated // The key is the client's IP address (expressed as 8 hex digits) and an 8 hex digit random string generated
// by world. // by world.
// //
char CombinedKey[17]; char combinedKey[17];
if(RuleB(Chat, EnableMailKeyIPVerification) == true) if(RuleB(Chat, EnableMailKeyIPVerification) == true)
sprintf(CombinedKey, "%08X%s", IPAddress, MailKey.c_str()); sprintf(combinedKey, "%08X%s", IPAddress, MailKey.c_str());
else 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); _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;
return !strcmp(row[0], combinedKey);
} }
int Database::FindCharacter(const char *CharacterName) { int Database::FindCharacter(const char *characterName) {
char errbuf[MYSQL_ERRMSG_SIZE]; char *safeCharName = RemoveApostrophes(characterName);
char* query = 0; std::string query = StringFormat("SELECT `id` FROM `character_` WHERE `name`='%s' LIMIT 1", safeCharName);
MYSQL_RES *result; auto results = QueryDatabase(query);
MYSQL_ROW row; if (!results.Success()) {
_log(UCS__ERROR, "FindCharacter failed. %s %s", query.c_str(), results.ErrorMessage().c_str());
char *SafeCharName = RemoveApostrophes(CharacterName); safe_delete(safeCharName);
return -1;
if (!RunQuery(query,MakeAnyLenString(&query, "select `id` from `character_` where `name`='%s' limit 1", }
SafeCharName),errbuf,&result)){ safe_delete(safeCharName);
_log(UCS__ERROR, "FindCharacter failed. %s %s", query, errbuf);
safe_delete_array(query);
safe_delete_array(SafeCharName);
if (results.RowCount() != 1) {
_log(UCS__ERROR, "Bad result from FindCharacter query for character %s", characterName);
return -1; return -1;
} }
safe_delete_array(query); auto row = results.begin();
safe_delete_array(SafeCharName);
if (mysql_num_rows(result) != 1) { int characterID = atoi(row[0]);
_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;
return characterID;
} }
bool Database::GetVariable(const char* varname, char* varvalue, uint16 varvalue_len) { bool Database::GetVariable(const char* varname, char* varvalue, uint16 varvalue_len) {
char errbuf[MYSQL_ERRMSG_SIZE]; std::string query = StringFormat("SELECT `value` FROM `variables` WHERE `varname` = '%s'", varname);
char* query = 0; auto results = QueryDatabase(query);
MYSQL_RES *result; if (!results.Success()) {
MYSQL_ROW row; _log(UCS__ERROR, "Unable to get message count from database. %s %s", query.c_str(), results.ErrorMessage().c_str());
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; return false;
} }
safe_delete_array(query); if (results.RowCount() != 1)
if (mysql_num_rows(result) != 1) {
mysql_free_result(result);
return false; return false;
}
row = mysql_fetch_row(result); auto row = results.begin();
snprintf(varvalue, varvalue_len, "%s", row[0]); snprintf(varvalue, varvalue_len, "%s", row[0]);
mysql_free_result(result);
return true; return true;
} }
@ -311,302 +246,238 @@ bool Database::LoadChatChannels() {
_log(UCS__INIT, "Loading chat channels from the database."); _log(UCS__INIT, "Loading chat channels from the database.");
char errbuf[MYSQL_ERRMSG_SIZE]; const std::string query = "SELECT `name`, `owner`, `password`, `minstatus` FROM `chatchannels`";
char* query = 0; auto results = QueryDatabase(query);
MYSQL_RES *result; if (!results.Success()) {
MYSQL_ROW row; _log(UCS__ERROR, "Failed to load channels. %s %s", query.c_str(), results.ErrorMessage().c_str());
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);
return false; 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))) { ChannelList->CreateChannel(channelName, channelOwner, channelPassword, true, atoi(row[3]));
std::string ChannelName = row[0];
std::string ChannelOwner = row[1];
std::string ChannelPassword = row[2];
ChannelList->CreateChannel(ChannelName, ChannelOwner, ChannelPassword, true, atoi(row[3]));
} }
mysql_free_result(result);
return true; 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]; std::string query = StringFormat("UPDATE `chatchannels` SET `password` = '%s' WHERE `name` = '%s'",
char *query = 0; 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]; std::string query = StringFormat("UPDATE `chatchannels` SET `owner` = '%s' WHERE `name` = '%s'",
char *query = 0; 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 unknownField2 = 25015275;
int UnknownField3 = 1; 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", client->MailBoxName().c_str(), characterID);
_log(UCS__TRACE, "Sendheaders for %s, CharID is %i", c->MailBoxName().c_str(), CharacterID);
if(CharacterID <= 0) if(characterID <= 0)
return; 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 buffer[100];
char* query = 0;
MYSQL_RES *result;
MYSQL_ROW row;
if (!RunQuery(query,MakeAnyLenString(&query, "select `msgid`,`timestamp`,`from`,`subject`, `status` from `mail` " int headerCountPacketLength = 0;
"where `charid`=%i", CharacterID),errbuf,&result)){
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()); VARSTRUCT_ENCODE_INTSTRING(packetBuffer, client->GetMailBoxNumber());
HeaderCountPacketLength += (strlen(Buf) + 1); VARSTRUCT_ENCODE_INTSTRING(packetBuffer, unknownField2);
VARSTRUCT_ENCODE_INTSTRING(packetBuffer, unknownField3);
sprintf(Buf, "%i", UnknownField2); VARSTRUCT_ENCODE_INTSTRING(packetBuffer, results.RowCount());
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);
_pkt(UCS__PACKETS, outapp); _pkt(UCS__PACKETS, outapp);
c->QueuePacket(outapp); client->QueuePacket(outapp);
safe_delete(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()); packetBuffer = (char *)outapp->pBuffer;
HeaderPacketLength = HeaderPacketLength + strlen(Buf) + 1;
sprintf(Buf, "%i", UnknownField2);
HeaderPacketLength = HeaderPacketLength + strlen(Buf) + 1;
sprintf(Buf, "%i", RowNum);
HeaderPacketLength = HeaderPacketLength + strlen(Buf) + 1;
HeaderPacketLength = HeaderPacketLength + strlen(row[0]) + 1; VARSTRUCT_ENCODE_INTSTRING(packetBuffer, client->GetMailBoxNumber());
HeaderPacketLength = HeaderPacketLength + strlen(row[1]) + 1; VARSTRUCT_ENCODE_INTSTRING(packetBuffer, unknownField2);
HeaderPacketLength = HeaderPacketLength + strlen(row[4]) + 1; VARSTRUCT_ENCODE_INTSTRING(packetBuffer, rowIndex);
HeaderPacketLength = HeaderPacketLength + GetMailPrefix().length() + strlen(row[2]) + 1; VARSTRUCT_ENCODE_STRING(packetBuffer, row[0]);
HeaderPacketLength = HeaderPacketLength + strlen(row[3]) + 1; VARSTRUCT_ENCODE_STRING(packetBuffer, row[1]);
VARSTRUCT_ENCODE_STRING(packetBuffer, row[4]);
outapp = new EQApplicationPacket(OP_MailHeader, HeaderPacketLength); VARSTRUCT_ENCODE_STRING(packetBuffer, GetMailPrefix().c_str());
packetBuffer--;
PacketBuffer = (char *)outapp->pBuffer; VARSTRUCT_ENCODE_STRING(packetBuffer, row[2]);
VARSTRUCT_ENCODE_STRING(packetBuffer, row[3]);
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]);
_pkt(UCS__PACKETS, outapp); _pkt(UCS__PACKETS, outapp);
c->QueuePacket(outapp); client->QueuePacket(outapp);
safe_delete(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; return;
char errbuf[MYSQL_ERRMSG_SIZE]; std::string query = StringFormat("SELECT `msgid`, `body`, `to` FROM `mail` "
char* query = 0; "WHERE `charid`=%i AND `msgid`=%i", characterID, messageNumber);
MYSQL_RES *result; auto results = QueryDatabase(query);
MYSQL_ROW row; if (!results.Success())
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);
return; 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()); EQApplicationPacket *outapp = new EQApplicationPacket(OP_MailSendBody,packetLength);
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);
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); _pkt(UCS__PACKETS, outapp);
c->QueuePacket(outapp); client->QueuePacket(outapp);
safe_delete(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()); if(lastPeriod == std::string::npos)
characterName = recipient;
std::string::size_type LastPeriod = Recipient.find_last_of(".");
if(LastPeriod == std::string::npos)
CharacterName = Recipient;
else 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++) for(unsigned int i = 1; i < characterName.length(); i++)
CharacterName[i] = tolower(CharacterName[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 *escSubject = new char[subject.length() * 2 + 1];
char* query = 0; char *escBody = new char[body.length() * 2 + 1];
char *EscSubject = new char[Subject.length() * 2 + 1]; DoEscapeString(escSubject, subject.c_str(), subject.length());
char *EscBody = new char[Body.length() * 2 + 1]; DoEscapeString(escBody, body.c_str(), body.length());
DoEscapeString(EscSubject, Subject.c_str(), Subject.length()); int now = time(nullptr); // time returns a 64 bit int on Windows at least, which vsnprintf doesn't like.
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);
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; 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) { if(client) {
std::string FQN = GetMailPrefix() + From; std::string FQN = GetMailPrefix() + from;
client->SendNotification(client->GetMailBoxNumber(characterName), subject, FQN, results.LastInsertedID());
c->SendNotification(c->GetMailBoxNumber(CharacterName), Subject, FQN, LastMsgID);
} }
MailMessagesSent++; MailMessagesSent++;
@ -614,156 +485,122 @@ bool Database::SendMail(std::string Recipient, std::string From, std::string Sub
return true; 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]; if(status == 0) {
char *query = 0; std::string query = StringFormat("DELETE FROM `mail` WHERE `msgid` = %i", messageNumber);
auto results = QueryDatabase(query);
return;
}
if(Status == 0) std::string query = StringFormat("UPDATE `mail` SET `status` = %i WHERE `msgid`=%i", status, messageNumber);
RunQuery(query, MakeAnyLenString(&query, "delete from `mail` where `msgid`=%i", MessageNumber), errbuf); auto results = QueryDatabase(query);
else if (!RunQuery(query, MakeAnyLenString(&query, "update `mail` set `status`=%i where `msgid`=%i", Status, MessageNumber), errbuf)) { 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() { void Database::ExpireMail() {
_log(UCS__INIT, "Expiring mail..."); _log(UCS__INIT, "Expiring mail...");
char errbuf[MYSQL_ERRMSG_SIZE]; std::string query = "SELECT COUNT(*) FROM `mail`";
char* query = 0; auto results = QueryDatabase(query);
MYSQL_RES *result; if (!results.Success()) {
MYSQL_ROW row; _log(UCS__ERROR, "Unable to get message count from database. %s %s", query.c_str(), results.ErrorMessage().c_str());
return;
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 ;
} }
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]); _log(UCS__INIT, "There are %s messages in the database.", row[0]);
mysql_free_result(result);
// Expire Trash // Expire Trash
if(RuleI(Mail, ExpireTrash) >= 0) { if(RuleI(Mail, ExpireTrash) >= 0) {
if(RunQuery(query, MakeAnyLenString(&query, "delete from `mail` where `status`=4 and `timestamp` < %i", query = StringFormat("DELETE FROM `mail` WHERE `status`=4 AND `timestamp` < %i",
time(nullptr) - RuleI(Mail, ExpireTrash)), errbuf, 0, &AffectedRows)) { time(nullptr) - RuleI(Mail, ExpireTrash));
_log(UCS__INIT, "Expired %i trash messages.", AffectedRows); results = QueryDatabase(query);
} if(!results.Success())
else { _log(UCS__ERROR, "Error expiring trash messages, %s %s", query.c_str(), results.ErrorMessage().c_str());
_log(UCS__ERROR, "Error expiring trash messages, %s %s", query, errbuf); else
} _log(UCS__INIT, "Expired %i trash messages.", results.RowsAffected());
safe_delete_array(query);
} }
// Expire Read // Expire Read
if(RuleI(Mail, ExpireRead) >= 0) { if(RuleI(Mail, ExpireRead) >= 0) {
if(RunQuery(query, MakeAnyLenString(&query, "delete from `mail` where `status`=3 and `timestamp` < %i", query = StringFormat("DELETE FROM `mail` WHERE `status` = 3 AND `timestamp` < %i",
time(nullptr) - RuleI(Mail, ExpireRead)), errbuf, 0, &AffectedRows)) { time(nullptr) - RuleI(Mail, ExpireRead));
_log(UCS__INIT, "Expired %i read messages.", AffectedRows); results = QueryDatabase(query);
} if(!results.Success())
else { _log(UCS__INIT, "Expired %i read messages.", results.RowsAffected());
_log(UCS__ERROR, "Error expiring read messages, %s %s", query, errbuf); else
} _log(UCS__ERROR, "Error expiring read messages, %s %s", query.c_str(), results.ErrorMessage().c_str());
safe_delete_array(query);
} }
// Expire Unread // Expire Unread
if(RuleI(Mail, ExpireUnread) >= 0) { if(RuleI(Mail, ExpireUnread) >= 0) {
if(RunQuery(query, MakeAnyLenString(&query, "delete from `mail` where `status`=1 and `timestamp` < %i", query = StringFormat("DELETE FROM `mail` WHERE `status`=1 AND `timestamp` < %i",
time(nullptr) - RuleI(Mail, ExpireUnread)), errbuf, 0, &AffectedRows)) { time(nullptr) - RuleI(Mail, ExpireUnread));
_log(UCS__INIT, "Expired %i unread messages.", AffectedRows); results = QueryDatabase(query);
} if(!results.Success())
else { _log(UCS__INIT, "Expired %i unread messages.", results.RowsAffected());
_log(UCS__ERROR, "Error expiring unread messages, %s %s", query, errbuf); else
} _log(UCS__ERROR, "Error expiring unread messages, %s %s", query.c_str(), results.ErrorMessage().c_str());
safe_delete_array(query);
} }
} }
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')"; std::string query = StringFormat("INSERT INTO `friends` (`charid`, `type`, `name`) "
"VALUES('%i', %i, '%s')",
char errbuf[MYSQL_ERRMSG_SIZE]; charID, type, CapitaliseName(name).c_str());
char* query = 0; 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());
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);
else else
_log(UCS__TRACE, "Wrote Friend/Ignore entry for charid %i, type %i, name %s to database.", _log(UCS__TRACE, "Wrote Friend/Ignore entry for charid %i, type %i, name %s to database.", charID, type, name.c_str());
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'"; std::string query = StringFormat("DELETE FROM `friends` WHERE `charid` = %i "
"AND `type` = %i AND `name` = '%s'",
char errbuf[MYSQL_ERRMSG_SIZE]; charID, type, CapitaliseName(name).c_str());
char* query = 0; auto results = QueryDatabase(query);
if(!results.Success())
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.c_str());
_log(UCS__ERROR, "Error removing friend/ignore, query was %s", query);
else else
_log(UCS__TRACE, "Removed Friend/Ignore entry for charid %i, type %i, name %s from database.", _log(UCS__TRACE, "Removed Friend/Ignore entry for charid %i, type %i, name %s from database.", charID, type, name.c_str());
CharID, Type, Name.c_str());
safe_delete_array(query);
} }
void Database::GetFriendsAndIgnore(int CharID, std::vector<std::string> &Friends, std::vector<std::string> &Ignorees) { void Database::GetFriendsAndIgnore(int charID, std::vector<std::string> &friends, std::vector<std::string> &ignorees) {
char errbuf[MYSQL_ERRMSG_SIZE]; std::string query = StringFormat("select `type`, `name` FROM `friends` WHERE `charid`=%i", charID);
char* query = 0; auto results = QueryDatabase(query);
MYSQL_RES *result; if (!results.Success()) {
MYSQL_ROW row; _log(UCS__ERROR, "GetFriendsAndIgnore query error %s, %s", query.c_str(), results.ErrorMessage().c_str());
return;
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 ;
} }
safe_delete_array(query);
while((row = mysql_fetch_row(result))) { for (auto row = results.begin(); row != results.end(); ++row) {
std::string name = row[1];
std::string Name = row[1];
if(atoi(row[0]) == 0) if(atoi(row[0]) == 0)
{ {
Ignorees.push_back(Name); ignorees.push_back(name);
_log(UCS__TRACE, "Added Ignoree from DB %s", Name.c_str()); _log(UCS__TRACE, "Added Ignoree from DB %s", name.c_str());
} continue;
else
{
Friends.push_back(Name);
_log(UCS__TRACE, "Added Friend from DB %s", Name.c_str());
} }
friends.push_back(name);
_log(UCS__TRACE, "Added Friend from DB %s", name.c_str());
} }
mysql_free_result(result);
return;
} }

View File

@ -877,8 +877,12 @@ bool Mob::CombatRange(Mob* other)
if (GetSpecialAbility(NPC_CHASE_DISTANCE)){ if (GetSpecialAbility(NPC_CHASE_DISTANCE)){
bool DoLoSCheck = true;
float max_dist = static_cast<float>(GetSpecialAbilityParam(NPC_CHASE_DISTANCE, 0)); float max_dist = static_cast<float>(GetSpecialAbilityParam(NPC_CHASE_DISTANCE, 0));
float min_dist = static_cast<float>(GetSpecialAbilityParam(NPC_CHASE_DISTANCE, 1)); float min_dist = static_cast<float>(GetSpecialAbilityParam(NPC_CHASE_DISTANCE, 1));
if (GetSpecialAbilityParam(NPC_CHASE_DISTANCE, 2));
DoLoSCheck = false; //Ignore line of sight check
if (max_dist == 1) if (max_dist == 1)
max_dist = 250.0f; //Default it to 250 if you forget to put a value max_dist = 250.0f; //Default it to 250 if you forget to put a value
@ -890,7 +894,7 @@ bool Mob::CombatRange(Mob* other)
else else
min_dist = min_dist * min_dist; min_dist = min_dist * min_dist;
if (CheckLastLosState() && (_DistNoRoot >= min_dist && _DistNoRoot <= max_dist)) if ((DoLoSCheck && CheckLastLosState()) && (_DistNoRoot >= min_dist && _DistNoRoot <= max_dist))
SetPseudoRoot(true); SetPseudoRoot(true);
else else
SetPseudoRoot(false); SetPseudoRoot(false);

View File

@ -268,7 +268,7 @@ public:
void TradeRequestFailed(const EQApplicationPacket* app); void TradeRequestFailed(const EQApplicationPacket* app);
void BuyTraderItem(TraderBuy_Struct* tbs,Client* trader,const EQApplicationPacket* app); void BuyTraderItem(TraderBuy_Struct* tbs,Client* trader,const EQApplicationPacket* app);
void TraderUpdate(uint16 slot_id,uint32 trader_id); 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<void*>* event_details = nullptr);
void SendZonePoints(); void SendZonePoints();
void SendBuyerResults(char *SearchQuery, uint32 SearchID); void SendBuyerResults(char *SearchQuery, uint32 SearchID);

View File

@ -3137,6 +3137,7 @@ void Client::Handle_OP_ItemLinkClick(const EQApplicationPacket *app)
DumpPacket(app); DumpPacket(app);
return; return;
} }
DumpPacket(app); DumpPacket(app);
ItemViewRequest_Struct* ivrs = (ItemViewRequest_Struct*)app->pBuffer; ItemViewRequest_Struct* ivrs = (ItemViewRequest_Struct*)app->pBuffer;
@ -3156,30 +3157,24 @@ void Client::Handle_OP_ItemLinkClick(const EQApplicationPacket *app)
silentsaylink = true; 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 (results.RowCount() != 1) {
{ Message(13, "Error: The saylink (%s) was not found in the database.", response.c_str());
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);
return; return;
} }
safe_delete_array(query);
auto row = results.begin();
response = row[0];
} }
if((response).size() > 0) if((response).size() > 0)
@ -4628,7 +4623,7 @@ void Client::Handle_OP_CastSpell(const EQApplicationPacket *app)
p_timers.Start(pTimerHarmTouch, HarmTouchReuseTime); p_timers.Start(pTimerHarmTouch, HarmTouchReuseTime);
} }
if (spell_to_cast > 0) // if we've matched LoH or HT, cast now if (spell_to_cast > 0) // if we've matched LoH or HT, cast now
CastSpell(spell_to_cast, castspell->target_id, castspell->slot); CastSpell(spell_to_cast, castspell->target_id, castspell->slot);
} }
@ -4862,6 +4857,7 @@ void Client::Handle_OP_TradeAcceptClick(const EQApplicationPacket *app)
{ {
Mob* with = trade->With(); Mob* with = trade->With();
trade->state = TradeAccepted; trade->state = TradeAccepted;
if (with && with->IsClient()) { if (with && with->IsClient()) {
//finish trade... //finish trade...
// Have both accepted? // Have both accepted?
@ -4872,6 +4868,7 @@ void Client::Handle_OP_TradeAcceptClick(const EQApplicationPacket *app)
other->trade->state = TradeCompleting; other->trade->state = TradeCompleting;
trade->state = TradeCompleting; trade->state = TradeCompleting;
// should we do this for NoDrop items as well?
if (CheckTradeLoreConflict(other) || other->CheckTradeLoreConflict(this)) { if (CheckTradeLoreConflict(other) || other->CheckTradeLoreConflict(this)) {
Message_StringID(13, TRADE_CANCEL_LORE); Message_StringID(13, TRADE_CANCEL_LORE);
other->Message_StringID(13, TRADE_CANCEL_LORE); other->Message_StringID(13, TRADE_CANCEL_LORE);
@ -4887,23 +4884,38 @@ void Client::Handle_OP_TradeAcceptClick(const EQApplicationPacket *app)
// start QS code // start QS code
if(RuleB(QueryServ, PlayerLogTrades)) { if(RuleB(QueryServ, PlayerLogTrades)) {
uint16 trade_count = 0; QSPlayerLogTrade_Struct event_entry;
std::list<void*> event_details;
// Item trade count for packet sizing memset(&event_entry, 0, sizeof(QSPlayerLogTrade_Struct));
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));
// Perform actual trade // Perform actual trade
this->FinishTrade(other, qspack, true); this->FinishTrade(other, true, &event_entry, &event_details);
other->FinishTrade(this, qspack, false); other->FinishTrade(this, false, &event_entry, &event_details);
qspack->Deflate(); event_entry._detail_count = event_details.size();
if(worldserver.Connected()) { worldserver.SendPacket(qspack); }
safe_delete(qspack); 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<void*>::iterator iter = event_details.begin(); iter != event_details.end(); ++iter, ++offset) {
QSTradeItems_Struct* detail = reinterpret_cast<QSTradeItems_Struct*>(*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 // end QS code
} }
else { else {
@ -4928,25 +4940,43 @@ void Client::Handle_OP_TradeAcceptClick(const EQApplicationPacket *app)
if(with->IsNPC()) { if(with->IsNPC()) {
// Audit trade to database for player trade stream // Audit trade to database for player trade stream
if(RuleB(QueryServ, PlayerLogHandins)) { if(RuleB(QueryServ, PlayerLogHandins)) {
uint16 handin_count = 0; QSPlayerLogHandin_Struct event_entry;
std::list<void*> event_details;
for(int16 slot_id = EmuConstants::TRADE_BEGIN; slot_id <= EmuConstants::TRADE_NPC_END; slot_id++) { memset(&event_entry, 0, sizeof(QSPlayerLogHandin_Struct));
if(m_inv[slot_id]) { handin_count += m_inv[slot_id]->GetTotalItemCount(); }
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<void*>::iterator iter = event_details.begin(); iter != event_details.end(); ++iter, ++offset) {
QSHandinItems_Struct* detail = reinterpret_cast<QSHandinItems_Struct*>(*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())
if(worldserver.Connected()) { worldserver.SendPacket(qspack); } worldserver.SendPacket(qs_pack);
safe_delete(qspack);
safe_delete(qs_pack);
} }
else { else {
FinishTrade(with->CastToNPC()); FinishTrade(with->CastToNPC());
} }
} }
#ifdef BOTS #ifdef BOTS
// TODO: Log Bot trades
else if(with->IsBot()) else if(with->IsBot())
with->CastToBot()->FinishTrade(this, Bot::BotTradeClientNormal); with->CastToBot()->FinishTrade(this, Bot::BotTradeClientNormal);
#endif #endif
@ -5864,9 +5894,9 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app)
else else
this->DeleteItemInInventory(mp->itemslot,mp->quantity,false); 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()) if(inst->IsCharged())
mp->quantity = 1; mp->quantity = 1;
EQApplicationPacket* outapp = new EQApplicationPacket(OP_ShopPlayerSell, sizeof(Merchant_Purchase_Struct)); EQApplicationPacket* outapp = new EQApplicationPacket(OP_ShopPlayerSell, sizeof(Merchant_Purchase_Struct));
Merchant_Purchase_Struct* mco=(Merchant_Purchase_Struct*)outapp->pBuffer; Merchant_Purchase_Struct* mco=(Merchant_Purchase_Struct*)outapp->pBuffer;
@ -7635,7 +7665,7 @@ void Client::Handle_OP_Mend(const EQApplicationPacket *app)
int mendhp = GetMaxHP() / 4; int mendhp = GetMaxHP() / 4;
int currenthp = GetHP(); int currenthp = GetHP();
if (MakeRandomInt(0, 199) < (int)GetSkill(SkillMend)) { if (MakeRandomInt(0, 199) < (int)GetSkill(SkillMend)) {
int criticalchance = spellbonuses.CriticalMend + itembonuses.CriticalMend + aabonuses.CriticalMend; int criticalchance = spellbonuses.CriticalMend + itembonuses.CriticalMend + aabonuses.CriticalMend;
if(MakeRandomInt(0,99) < criticalchance){ if(MakeRandomInt(0,99) < criticalchance){
@ -9532,7 +9562,7 @@ void Client::CompleteConnect() {
/* This sub event is for if a player logs in for the first time since entering world. */ /* This sub event is for if a player logs in for the first time since entering world. */
if (firstlogon == 1){ if (firstlogon == 1){
parse->EventPlayer(EVENT_CONNECT, this, "", 0); parse->EventPlayer(EVENT_CONNECT, this, "", 0);
/* QS: PlayerLogConnectDisconnect */ /* QS: PlayerLogConnectDisconnect */
if (RuleB(QueryServ, PlayerLogConnectDisconnect)){ if (RuleB(QueryServ, PlayerLogConnectDisconnect)){
std::string event_desc = StringFormat("Connect :: Logged into zoneid:%i instid:%i", this->GetZoneID(), this->GetInstanceID()); std::string event_desc = StringFormat("Connect :: Logged into zoneid:%i instid:%i", this->GetZoneID(), this->GetInstanceID());
@ -11410,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)); Message(15,"Your home city has already been set.", m_pp.binds[4].zoneId, database.GetZoneName(m_pp.binds[4].zoneId));
return; return;
} }
if (app->size < 1) { if (app->size < 1) {
LogFile->write(EQEMuLog::Error, "Wrong size: OP_SetStartCity, size=%i, expected %i", app->size, 1); LogFile->write(EQEMuLog::Error, "Wrong size: OP_SetStartCity, size=%i, expected %i", app->size, 1);
DumpPacket(app); DumpPacket(app);
return; return;
} }
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
MYSQL_RES *result = nullptr;
MYSQL_ROW row = 0;
float x(0),y(0),z(0); float x(0),y(0),z(0);
uint32 zoneid = 0; uint32 zoneid = 0;
uint32 startCity = (uint32)strtol((const char*)app->pBuffer, nullptr, 10);
uint32 StartCity = (uint32)strtol((const char*)app->pBuffer, nullptr, 10); std::string query = StringFormat("SELECT zone_id, bind_id, x, y, z FROM start_zones "
bool ValidCity = false; "WHERE player_class=%i AND player_deity=%i AND player_race=%i",
database.RunQuery m_pp.class_, m_pp.deity, m_pp.race);
( auto results = database.QueryDatabase(query);
query, if(!results.Success()) {
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) {
LogFile->write(EQEMuLog::Error, "No valid start zones found for /setstartcity"); LogFile->write(EQEMuLog::Error, "No valid start zones found for /setstartcity");
return; return;
} }
while(row = mysql_fetch_row(result)) { bool validCity = false;
for (auto row = results.begin(); row != results.end(); ++row) {
if(atoi(row[1]) != 0) if(atoi(row[1]) != 0)
zoneid = atoi(row[1]); zoneid = atoi(row[1]);
else else
zoneid = atoi(row[0]); zoneid = atoi(row[0]);
if(zoneid == StartCity) { if(zoneid != startCity)
ValidCity = true; continue;
x = atof(row[2]);
y = atof(row[3]); validCity = true;
z = atof(row[4]); x = atof(row[2]);
} y = atof(row[3]);
z = atof(row[4]);
} }
if(ValidCity) { if(validCity) {
Message(15,"Your home city has been set"); Message(15,"Your home city has been set");
SetStartZone(StartCity, x, y, z); SetStartZone(startCity, x, y, z);
} return;
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);
}
} }
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) void Client::Handle_OP_Report(const EQApplicationPacket *app)
@ -11602,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 // 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. // 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)) if(app->size < sizeof(GMSearchCorpse_Struct))
{ {
@ -11615,85 +11621,62 @@ void Client::Handle_OP_GMSearchCorpse(const EQApplicationPacket *app)
GMSearchCorpse_Struct *gmscs = (GMSearchCorpse_Struct *)app->pBuffer; GMSearchCorpse_Struct *gmscs = (GMSearchCorpse_Struct *)app->pBuffer;
gmscs->Name[63] = '\0'; gmscs->Name[63] = '\0';
char errbuf[MYSQL_ERRMSG_SIZE]; char *escSearchString = new char[129];
char* Query = 0; database.DoEscapeString(escSearchString, gmscs->Name, strlen(gmscs->Name));
MYSQL_RES *Result;
MYSQL_ROW Row;
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 " if(results.RowCount() == maxResults)
"player_corpses where charname like '%%%s%%' order by charname limit %i", Message(clientMessageError, "Your search found too many results; some are not displayed.");
EscSearchString, MaxResults), errbuf, &Result)) 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) std::string popupText = "<table><tr><td>Name</td><td>Zone</td><td>X</td><td>Y</td><td>Z</td><td>Date</td><td>"
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 = "<table><tr><td>Name</td><td>Zone</td><td>X</td><td>Y</td><td>Z</td><td>Date</td><td>"
"Rezzed</td><td>Buried</td></tr><tr><td>&nbsp</td><td></td><td></td><td></td><td></td><td>" "Rezzed</td><td>Buried</td></tr><tr><td>&nbsp</td><td></td><td></td><td></td><td></td><td>"
"</td><td></td><td></td></tr>"; "</td><td></td><td></td></tr>";
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]); bool corpseRezzed = atoi(row[6]);
float CorpseY = atof(Row[3]); bool corpseBuried = atoi(row[7]);
float CorpseZ = atof(Row[4]);
strn0cpy(TimeOfDeath, Row[5], sizeof(TimeOfDeath)); popupText += StringFormat("<tr><td>%s</td><td>%s</td><td>%8.0f</td><td>%8.0f</td><td>%8.0f</td><td>%s</td><td>%s</td><td>%s</td></tr>",
charName, StaticGetZoneName(ZoneID), CorpseX, CorpseY, CorpseZ, timeOfDeath,
corpseRezzed ? "Yes" : "No", corpseBuried ? "Yes" : "No");
bool CorpseRezzed = atoi(Row[6]); if(popupText.size() > 4000) {
bool CorpseBuried = atoi(Row[7]); Message(clientMessageError, "Unable to display all the results.");
break;
}
sprintf(Buffer, "<tr><td>%s</td><td>%s</td><td>%8.0f</td><td>%8.0f</td><td>%8.0f</td><td>%s</td><td>%s</td><td>%s</td></tr>", }
CharName, StaticGetZoneName(ZoneID), CorpseX, CorpseY, CorpseZ, TimeOfDeath,
CorpseRezzed ? "Yes" : "No", CorpseBuried ? "Yes" : "No");
PopupText += Buffer; popupText += "</table>";
if(PopupText.size() > 4000) SendPopupToClient("Corpses", popupText.c_str());
{
Message(clientMessageError, "Unable to display all the results.");
break;
}
}
PopupText += "</table>";
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) void Client::Handle_OP_GuildBank(const EQApplicationPacket *app)
@ -12775,7 +12758,7 @@ void Client::Handle_OP_AltCurrencyReclaim(const EQApplicationPacket *app) {
QServ->PlayerLogEvent(Player_Log_Alternate_Currency_Transactions, this->CharacterID(), event_desc); QServ->PlayerLogEvent(Player_Log_Alternate_Currency_Transactions, this->CharacterID(), event_desc);
} }
} }
} }
/* Cursor to Item storage */ /* Cursor to Item storage */
else { else {
uint32 max_currency = GetAlternateCurrencyValue(reclaim->currency_id); uint32 max_currency = GetAlternateCurrencyValue(reclaim->currency_id);
@ -12784,7 +12767,7 @@ void Client::Handle_OP_AltCurrencyReclaim(const EQApplicationPacket *app) {
if(reclaim->count > max_currency) { if(reclaim->count > max_currency) {
SummonItem(item_id, max_currency); SummonItem(item_id, max_currency);
SetAlternateCurrencyValue(reclaim->currency_id, 0); SetAlternateCurrencyValue(reclaim->currency_id, 0);
} }
else { else {
SummonItem(item_id, reclaim->count, 0, 0, 0, 0, 0, false, MainCursor); SummonItem(item_id, reclaim->count, 0, 0, 0, 0, 0, false, MainCursor);
AddAlternateCurrencyValue(reclaim->currency_id, -((int32)reclaim->count)); AddAlternateCurrencyValue(reclaim->currency_id, -((int32)reclaim->count));
@ -12793,7 +12776,7 @@ void Client::Handle_OP_AltCurrencyReclaim(const EQApplicationPacket *app) {
if (RuleB(QueryServ, PlayerLogAlternateCurrencyTransactions)){ 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()); 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); QServ->PlayerLogEvent(Player_Log_Alternate_Currency_Transactions, this->CharacterID(), event_desc);
} }
} }
} }
@ -12885,8 +12868,8 @@ void Client::Handle_OP_AltCurrencySell(const EQApplicationPacket *app) {
/* QS: PlayerLogAlternateCurrencyTransactions :: Sold to Merchant*/ /* QS: PlayerLogAlternateCurrencyTransactions :: Sold to Merchant*/
if (RuleB(QueryServ, PlayerLogAlternateCurrencyTransactions)){ 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()); 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); QServ->PlayerLogEvent(Player_Log_Alternate_Currency_Transactions, this->CharacterID(), event_desc);
} }
FastQueuePacket(&outapp); FastQueuePacket(&outapp);
AddAlternateCurrencyValue(alt_cur_id, cost); AddAlternateCurrencyValue(alt_cur_id, cost);
@ -12947,7 +12930,7 @@ void Client::Handle_OP_LFGuild(const EQApplicationPacket *app)
switch(Command) switch(Command)
{ {
case 0: case 0:
{ {
VERIFY_PACKET_LENGTH(OP_LFGuild, app, LFGuild_PlayerToggle_Struct); VERIFY_PACKET_LENGTH(OP_LFGuild, app, LFGuild_PlayerToggle_Struct);
LFGuild_PlayerToggle_Struct *pts = (LFGuild_PlayerToggle_Struct *)app->pBuffer; LFGuild_PlayerToggle_Struct *pts = (LFGuild_PlayerToggle_Struct *)app->pBuffer;

View File

@ -527,7 +527,7 @@ public:
Mob* With(); Mob* With();
// Add item from cursor slot to trade bucket (automatically does bag data too) // 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 // Audit trade
void LogTrade(); void LogTrade();

View File

@ -568,157 +568,137 @@ void Doors::DumpDoor(){
} }
int32 ZoneDatabase::GetDoorsCount(uint32* oMaxID, const char *zone_name, int16 version) { int32 ZoneDatabase::GetDoorsCount(uint32* oMaxID, const char *zone_name, int16 version) {
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
MYSQL_RES *result; std::string query = StringFormat("SELECT MAX(id), count(*) FROM doors "
MYSQL_ROW row; "WHERE zone = '%s' AND (version = %u OR version = -1)",
query = new char[256]; zone_name, version);
sprintf(query, "SELECT MAX(id), count(*) FROM doors WHERE zone='%s' AND (version=%u OR version=-1)", zone_name, version); auto results = QueryDatabase(query);
if (RunQuery(query, strlen(query), errbuf, &result)) { if (!results.Success()) {
safe_delete_array(query); std::cerr << "Error in GetDoorsCount query '" << query << "' " << results.ErrorMessage() << std::endl;
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);
return -1; 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) { int32 ZoneDatabase::GetDoorsCountPlusOne(const char *zone_name, int16 version) {
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
uint32 oMaxID = 0;
MYSQL_RES *result; std::string query = StringFormat("SELECT MAX(id) FROM doors "
MYSQL_ROW row; "WHERE zone = '%s' AND version = %u", zone_name, version);
query = new char[256]; auto results = QueryDatabase(query);
sprintf(query, "SELECT MAX(id) FROM doors WHERE zone='%s' AND version=%u", zone_name, version); if (!results.Success()) {
if (RunQuery(query, strlen(query), errbuf, &result)) { std::cerr << "Error in GetDoorsCountPlusOne query '" << query << "' " << results.ErrorMessage() << std::endl;
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);
return -1; 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) { int32 ZoneDatabase::GetDoorsDBCountPlusOne(const char *zone_name, int16 version) {
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
uint32 oMaxID = 0; uint32 oMaxID = 0;
MYSQL_RES *result; std::string query = StringFormat("SELECT MAX(doorid) FROM doors "
MYSQL_ROW row; "WHERE zone = '%s' AND (version = %u OR version = -1)",
query = new char[256]; zone_name, version);
sprintf(query, "SELECT MAX(doorid) FROM doors WHERE zone='%s' AND (version=%u OR version=-1)", zone_name, version); auto results = QueryDatabase(query);
if (RunQuery(query, strlen(query), errbuf, &result)) { if (!results.Success()) {
safe_delete_array(query); std::cerr << "Error in GetDoorsCountPlusOne query '" << query << "' " << results.ErrorMessage() << std::endl;
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);
return -1; 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) { bool ZoneDatabase::LoadDoors(int32 iDoorCount, Door *into, const char *zone_name, int16 version) {
LogFile->write(EQEMuLog::Status, "Loading Doors from database..."); LogFile->write(EQEMuLog::Status, "Loading Doors from database...");
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
MYSQL_RES *result;
MYSQL_ROW row;
// Door tmpDoor; // Door tmpDoor;
MakeAnyLenString(&query, "SELECT id,doorid,zone,name,pos_x,pos_y,pos_z,heading," 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," "opentype, guild, lockpick, keyitem, nokeyring, triggerdoor, triggertype, "
"dest_y,dest_z,dest_heading,door_param,invert_state,incline,size,is_ldon_door,client_version_mask " "dest_zone, dest_instance, dest_x, dest_y, dest_z, dest_heading, "
"FROM doors WHERE zone='%s' AND (version=%u OR version=-1) ORDER BY doorid asc", zone_name, version); "door_param, invert_state, incline, size, is_ldon_door, client_version_mask "
if (RunQuery(query, strlen(query), errbuf, &result)) { "FROM doors WHERE zone = '%s' AND (version = %u OR version = -1) "
safe_delete_array(query); "ORDER BY doorid asc", zone_name, version);
int32 r; auto results = QueryDatabase(query);
for(r = 0; (row = mysql_fetch_row(result)); r++) { if (!results.Success()){
if(r >= iDoorCount) { std::cerr << "Error in DBLoadDoors query '" << query << "' " << results.ErrorMessage() << std::endl;
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);
return false; 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; return true;
} }

View File

@ -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 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) bool GuildBankManager::IsLoaded(uint32 GuildID)
@ -973,156 +935,127 @@ bool GuildBankManager::AddItem(uint32 GuildID, uint8 Area, uint32 ItemID, int32
return true; 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; return -1;
std::list<GuildBank*>::iterator Iterator = GetGuildBank(GuildID); auto iter = GetGuildBank(guildID);
if(Iterator == Banks.end()) if(iter == Banks.end())
{
return -1; return -1;
}
if((*Iterator)->Items.DepositArea[SlotID].ItemID == 0) if((*iter)->Items.DepositArea[slotID].ItemID == 0)
{
return -1; return -1;
}
int MainSlot = -1; int mainSlot = -1;
for(int i = 0; i < GUILD_BANK_MAIN_AREA_SIZE; ++i) for(int i = 0; i < GUILD_BANK_MAIN_AREA_SIZE; ++i)
if((*Iterator)->Items.MainArea[i].ItemID == 0) if((*iter)->Items.MainArea[i].ItemID == 0) {
{ mainSlot = i;
MainSlot = i;
break; break;
} }
if(MainSlot == -1) if(mainSlot == -1)
return -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; 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));
(*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);
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; return -1;
} }
safe_delete_array(query); (*iter)->Items.DepositArea[slotID].ItemID = 0;
(*Iterator)->Items.DepositArea[SlotID].ItemID = 0; const Item_Struct *Item = database.GetItem((*iter)->Items.MainArea[mainSlot].ItemID);
const Item_Struct *Item = database.GetItem((*Iterator)->Items.MainArea[MainSlot].ItemID);
GuildBankItemUpdate_Struct gbius; GuildBankItemUpdate_Struct gbius;
if(!Item->Stackable) 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 else
{ {
if((*Iterator)->Items.MainArea[MainSlot].Quantity == Item->StackSize) if((*iter)->Items.MainArea[mainSlot].Quantity == Item->StackSize)
gbius.Init(GuildBankItemUpdate, 1, MainSlot, GuildBankMainArea, 1, Item->ID, Item->Icon, gbius.Init(GuildBankItemUpdate, 1, mainSlot, GuildBankMainArea, 1, Item->ID, Item->Icon,
(*Iterator)->Items.MainArea[MainSlot].Quantity, 0, 0, 0); (*iter)->Items.MainArea[mainSlot].Quantity, 0, 0, 0);
else else
gbius.Init(GuildBankItemUpdate, 1, MainSlot, GuildBankMainArea, 1, Item->ID, Item->Icon, gbius.Init(GuildBankItemUpdate, 1, mainSlot, GuildBankMainArea, 1, Item->ID, Item->Icon,
(*Iterator)->Items.MainArea[MainSlot].Quantity, 0, 1, 0); (*iter)->Items.MainArea[mainSlot].Quantity, 0, 1, 0);
} }
strn0cpy(gbius.ItemName, Item->Name, sizeof(gbius.ItemName)); 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; return;
std::list<GuildBank*>::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; return;
} }
if((*Iterator)->Items.MainArea[SlotID].ItemID == 0) (*iter)->Items.MainArea[slotID].Permissions = permissions;
{
return;
}
const char *Query="UPDATE `guild_bank` SET `permissions` = %i, `whofor` = '%s' WHERE `guildid` = %i AND `area` = 1 AND `slot` = %i LIMIT 1"; if(permissions == GuildBankSingleMember)
strn0cpy((*iter)->Items.MainArea[slotID].WhoFor, memberName, sizeof((*iter)->Items.MainArea[slotID].WhoFor));
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));
else else
(*Iterator)->Items.MainArea[SlotID].WhoFor[0] = '\0'; (*iter)->Items.MainArea[slotID].WhoFor[0] = '\0';
const Item_Struct *Item = database.GetItem((*iter)->Items.MainArea[slotID].ItemID);
const Item_Struct *Item = database.GetItem((*Iterator)->Items.MainArea[SlotID].ItemID);
GuildBankItemUpdate_Struct gbius; GuildBankItemUpdate_Struct gbius;
if(!Item->Stackable) 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 else
{ {
if((*Iterator)->Items.MainArea[SlotID].Quantity == Item->StackSize) if((*iter)->Items.MainArea[slotID].Quantity == Item->StackSize)
gbius.Init(GuildBankItemUpdate, 1, SlotID, GuildBankMainArea, 1, Item->ID, Item->Icon, gbius.Init(GuildBankItemUpdate, 1, slotID, GuildBankMainArea, 1, Item->ID, Item->Icon,
(*Iterator)->Items.MainArea[SlotID].Quantity, (*Iterator)->Items.MainArea[SlotID].Permissions, 0, 0); (*iter)->Items.MainArea[slotID].Quantity, (*iter)->Items.MainArea[slotID].Permissions, 0, 0);
else else
gbius.Init(GuildBankItemUpdate, 1, SlotID, GuildBankMainArea, 1, Item->ID, Item->Icon, gbius.Init(GuildBankItemUpdate, 1, slotID, GuildBankMainArea, 1, Item->ID, Item->Icon,
(*Iterator)->Items.MainArea[SlotID].Quantity, (*Iterator)->Items.MainArea[SlotID].Permissions, 1, 0); (*iter)->Items.MainArea[slotID].Quantity, (*iter)->Items.MainArea[slotID].Permissions, 1, 0);
} }
strn0cpy(gbius.ItemName, Item->Name, sizeof(gbius.ItemName)); 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) ItemInst* GuildBankManager::GetItem(uint32 GuildID, uint16 Area, uint16 SlotID, uint32 Quantity)
@ -1208,90 +1141,73 @@ std::list<GuildBank*>::iterator GuildBankManager::GetGuildBank(uint32 GuildID)
return Iterator; 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<GuildBank*>::iterator Iterator = GetGuildBank(GuildID); auto iter = GetGuildBank(guildID);
if(Iterator == Banks.end()) if(iter == Banks.end())
return false; return false;
char errbuf[MYSQL_ERRMSG_SIZE];
char* query = 0;
GuildBankItem* BankArea = nullptr; 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; return false;
BankArea = &(*Iterator)->Items.MainArea[0]; BankArea = &(*iter)->Items.MainArea[0];
} } else {
else if(slotID > (GUILD_BANK_DEPOSIT_AREA_SIZE - 1))
{
if(SlotID > (GUILD_BANK_DEPOSIT_AREA_SIZE - 1))
return false; 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);
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);
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; return false;
} }
safe_delete_array(query); BankArea[slotID].ItemID = 0;
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);
} 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; return false;
} }
safe_delete_array(query); BankArea[slotID].Quantity -= quantity;
BankArea[SlotID].Quantity -= Quantity; deleted = false;
Deleted = false;
} }
GuildBankItemUpdate_Struct gbius; 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.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 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; return true;
@ -1422,26 +1338,20 @@ bool GuildBankManager::SplitStack(uint32 GuildID, uint16 SlotID, uint32 Quantity
return true; 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. // Helper method for MergeStacks. Assuming all passed parameters are valid.
// //
char errbuf[MYSQL_ERRMSG_SIZE]; std::string query = StringFormat("UPDATE `guild_bank` SET `qty` = %i "
"WHERE `guildid` = %i AND `area` = %i "
char* query = 0; "AND `slot` = %i LIMIT 1",
quantity, guildID, area, slotID);
const char *Query = "UPDATE `guild_bank` SET `qty` = %i where `guildid` = %i AND `area` = %i AND `slot` = %i LIMIT 1"; auto results = database.QueryDatabase(query);
if(!results.Success()) {
if(!database.RunQuery(query, MakeAnyLenString(&query, Query, Quantity, GuildID, Area, SlotID), errbuf)) _log(GUILDS__BANK_ERROR, "Update item quantity failed. %s : %s", query.c_str(), results.ErrorMessage().c_str());
{
_log(GUILDS__BANK_ERROR, "Update item quantity failed. %s : %s", query, errbuf);
safe_delete_array(query);
return; return;
} }
safe_delete_array(query);
} }
bool GuildBankManager::AllowedToWithdraw(uint32 GuildID, uint16 Area, uint16 SlotID, const char *Name) bool GuildBankManager::AllowedToWithdraw(uint32 GuildID, uint16 Area, uint16 SlotID, const char *Name)

View File

@ -822,25 +822,24 @@ bool Client::PushItemOnCursor(const ItemInst& inst, bool client_update)
return database.SaveCursor(CharacterID(), s, e); 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); mlog(INVENTORY__SLOTS, "Putting item %s (%d) into slot %d", inst.GetItem()->Name, inst.GetItem()->ID, slot_id);
if (slot_id == MainCursor) if (slot_id == MainCursor)
{ return PushItemOnCursor(inst, client_update);
return PushItemOnCursor(inst,client_update);
}
else else
m_inv.PutItem(slot_id, inst); m_inv.PutItem(slot_id, inst);
if (client_update) { if (client_update)
SendItemPacket(slot_id, &inst, (slot_id == MainCursor) ? ItemPacketSummonItem : ItemPacketTrade); SendItemPacket(slot_id, &inst, ((slot_id == MainCursor) ? ItemPacketSummonItem : ItemPacketTrade));
}
if (slot_id == MainCursor) { if (slot_id == MainCursor) {
std::list<ItemInst*>::const_iterator s=m_inv.cursor_begin(),e=m_inv.cursor_end(); std::list<ItemInst*>::const_iterator s = m_inv.cursor_begin(), e = m_inv.cursor_end();
return database.SaveCursor(this->CharacterID(), s, e); return database.SaveCursor(this->CharacterID(), s, e);
} else }
else {
return database.SaveInventory(this->CharacterID(), &inst, slot_id); return database.SaveInventory(this->CharacterID(), &inst, slot_id);
}
CalcBonuses(); CalcBonuses();
} }
@ -1539,7 +1538,7 @@ bool Client::SwapItem(MoveItem_Struct* move_in) {
// Also sends trade information to other client of trade session // Also sends trade information to other client of trade session
if(RuleB(QueryServ, PlayerLogMoves)) { QSSwapItemAuditor(move_in); } // QS Audit 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; return true;
} else { } else {

View File

@ -1787,252 +1787,49 @@ void NPC::ModifyNPCStat(const char *identifier, const char *newValue)
{ {
std::string id = identifier; std::string id = identifier;
std::string val = newValue; 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]); id[i] = std::tolower(id[i]);
} }
if(id == "ac") if(id == "ac") { AC = atoi(val.c_str()); return; }
{ else if(id == "str") { STR = atoi(val.c_str()); return; }
AC = atoi(val.c_str()); else if(id == "sta") { STA = atoi(val.c_str()); return; }
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; }
if(id == "str") else if(id == "int" || id == "_int") { INT = atoi(val.c_str()); CalcMaxMana(); return; }
{ else if(id == "cha") { CHA = atoi(val.c_str()); return; }
STR = atoi(val.c_str()); else if(id == "max_hp") { base_hp = atoi(val.c_str()); CalcMaxHP(); if (cur_hp > max_hp) { cur_hp = max_hp; } return; }
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; }
if(id == "sta") else if(id == "cr") { CR = atoi(val.c_str()); return; }
{ else if(id == "pr") { PR = atoi(val.c_str()); return; }
STA = atoi(val.c_str()); else if(id == "dr") { DR = atoi(val.c_str()); return; }
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; }
if(id == "agi") else if(id == "attack_speed") { attack_speed = (float)atof(val.c_str()); CalcBonuses(); return; }
{ else if(id == "atk") { ATK = atoi(val.c_str()); return; }
AGI = atoi(val.c_str()); else if(id == "accuracy") { accuracy_rating = atoi(val.c_str()); return; }
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; }
if(id == "dex") else if(id == "max_hit") { max_dmg = atoi(val.c_str()); return; }
{ else if(id == "attack_count") { attack_count = atoi(val.c_str()); return; }
DEX = atoi(val.c_str()); else if(id == "see_invis") { see_invis = atoi(val.c_str()); return; }
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; }
if(id == "wis") else if(id == "hp_regen") { hp_regen = atoi(val.c_str()); return; }
{ else if(id == "mana_regen") { mana_regen = atoi(val.c_str()); return; }
WIS = atoi(val.c_str()); else if(id == "level") { SetLevel(atoi(val.c_str())); return; }
CalcMaxMana(); else if(id == "aggro") { pAggroRange = atof(val.c_str()); return; }
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; }
if(id == "int" || id == "_int") else if(id == "healscale") { healscale = atof(val.c_str()); return; }
{ else if(id == "spellscale") { spellscale = atof(val.c_str()); return; }
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;
}
} }
void NPC::LevelScale() { void NPC::LevelScale() {

View File

@ -354,96 +354,86 @@ void Spawn2::DeathReset(bool realdeath)
} }
bool ZoneDatabase::PopulateZoneSpawnList(uint32 zoneid, LinkedList<Spawn2*> &spawn2_list, int16 version, uint32 repopdelay) { bool ZoneDatabase::PopulateZoneSpawnList(uint32 zoneid, LinkedList<Spawn2*> &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); const char *zone_name = database.GetZoneName(zoneid);
std::string query = StringFormat("SELECT id, spawngroupID, x, y, z, heading, "
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); "respawntime, variance, pathgrid, _condition, "
if (RunQuery(query, strlen(query), errbuf, &result)) "cond_value, enabled, animation FROM spawn2 "
{ "WHERE zone = '%s' AND version = %u",
safe_delete_array(query); zone_name, version);
while((row = mysql_fetch_row(result))) auto results = QueryDatabase(query);
{ if (!results.Success()) {
Spawn2* newSpawn = 0; LogFile->write(EQEMuLog::Error, "Error in PopulateZoneLists query '%s': %s", query.c_str(), results.ErrorMessage().c_str());
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);
return false; 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; return true;
} }
Spawn2* ZoneDatabase::LoadSpawn2(LinkedList<Spawn2*> &spawn2_list, uint32 spawn2id, uint32 timeleft) { Spawn2* ZoneDatabase::LoadSpawn2(LinkedList<Spawn2*> &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)) { std::string query = StringFormat("SELECT id, spawngroupID, x, y, z, heading, "
if (mysql_num_rows(result) == 1) "respawntime, variance, pathgrid, _condition, "
{ "cond_value, enabled, animation FROM spawn2 "
row = mysql_fetch_row(result); "WHERE id = %i", spawn2id);
bool perl_enabled = atoi(row[11]) == 1 ? true : false; auto results = QueryDatabase(query);
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])); if (!results.Success()) {
spawn2_list.Insert( newSpawn ); LogFile->write(EQEMuLog::Error, "Error in LoadSpawn2 query '%s': %s", query.c_str(), results.ErrorMessage().c_str());
mysql_free_result(result); return nullptr;
safe_delete_array(query); }
return newSpawn;
}
mysql_free_result(result);
}
LogFile->write(EQEMuLog::Error, "Error in LoadSpawn2 query '%s': %s", query, errbuf); if (results.RowCount() != 1) {
safe_delete_array(query); LogFile->write(EQEMuLog::Error, "Error in LoadSpawn2 query '%s': %s", query.c_str(), results.ErrorMessage().c_str());
return 0; 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; std::string query = StringFormat("INSERT INTO spawn2 (spawngroupID, zone, x, y, z, heading, "
uint32 affected_rows = 0; "respawntime, variance, _condition, cond_value) "
"VALUES (%i, '%s', %f, %f, %f, %f, %i, %i, %u, %i)",
// if(GetInverseXY()==1) { spawngroup, zone, x, y, z, heading,
// float temp=x; respawn, variance, condition, cond_value);
// x=y; auto results = QueryDatabase(query);
// y=temp; if (!results.Success()) {
// } LogFile->write(EQEMuLog::Error, "Error in CreateSpawn2 query '%s': %s", query.c_str(), results.ErrorMessage().c_str());
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);
return false; return false;
} }
return false; if (results.RowsAffected() != 1)
return false;
if(client)
client->LogSQL(query.c_str());
return true;
} }
uint32 Zone::CountSpawn2() { uint32 Zone::CountSpawn2() {
@ -671,177 +661,159 @@ void SpawnConditionManager::ExecEvent(SpawnEvent &event, bool send_update) {
} }
void SpawnConditionManager::UpdateDBEvent(SpawnEvent &event) { void SpawnConditionManager::UpdateDBEvent(SpawnEvent &event) {
char errbuf[MYSQL_ERRMSG_SIZE];
char* query = 0;
int len;
SpawnCondition cond; std::string query = StringFormat("UPDATE spawn_events SET "
len = MakeAnyLenString(&query, "next_minute = %d, next_hour = %d, "
"UPDATE spawn_events SET " "next_day = %d, next_month = %d, "
"next_minute=%d, next_hour=%d, next_day=%d, next_month=%d, " "next_year = %d, enabled = %d, "
"next_year=%d, enabled=%d, strict=%d " "strict = %d WHERE id = %d",
"WHERE id=%d", event.next.minute, event.next.hour,
event.next.minute, event.next.hour, event.next.day, event.next.month, event.next.day, event.next.month,
event.next.year, event.enabled?1:0, event.strict?1:0,event.id event.next.year, event.enabled? 1: 0,
); event.strict? 1: 0, event.id);
if(!database.RunQuery(query, len, errbuf)) { auto results = database.QueryDatabase(query);
LogFile->write(EQEMuLog::Error, "Unable to update spawn event '%s': %s\n", query, errbuf); if(!results.Success())
} LogFile->write(EQEMuLog::Error, "Unable to update spawn event '%s': %s\n", query.c_str(), results.ErrorMessage().c_str());
safe_delete_array(query);
} }
void SpawnConditionManager::UpdateDBCondition(const char* zone_name, uint32 instance_id, uint16 cond_id, int16 value) { 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; std::string query = StringFormat("REPLACE INTO spawn_condition_values "
len = MakeAnyLenString(&query, "(id, value, zone, instance_id) "
"REPLACE INTO spawn_condition_values (id, value, zone, instance_id) VALUES(%u, %u, '%s', %u)", "VALUES( %u, %u, '%s', %u)",
cond_id, value, zone_name, instance_id cond_id, value, zone_name, instance_id);
); auto results = database.QueryDatabase(query);
if(!database.RunQuery(query, len, errbuf)) { if(!results.Success())
LogFile->write(EQEMuLog::Error, "Unable to update spawn condition '%s': %s\n", query, errbuf); LogFile->write(EQEMuLog::Error, "Unable to update spawn condition '%s': %s\n", query.c_str(), results.ErrorMessage().c_str());
}
safe_delete_array(query);
} }
bool SpawnConditionManager::LoadDBEvent(uint32 event_id, SpawnEvent &event, std::string &zone_name) { 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; std::string query = StringFormat("SELECT id, cond_id, period, "
"next_minute, next_hour, next_day, "
len = MakeAnyLenString(&query, "next_month, next_year, enabled, "
"SELECT id,cond_id,period,next_minute,next_hour,next_day,next_month,next_year,enabled,action,argument,strict,zone " "action, argument, strict, zone "
"FROM spawn_events WHERE id=%d", event_id); "FROM spawn_events WHERE id = %d", event_id);
if (database.RunQuery(query, len, errbuf, &result)) { auto results = database.QueryDatabase(query);
safe_delete_array(query); if (!results.Success()) {
if((row = mysql_fetch_row(result))) { LogFile->write(EQEMuLog::Error, "Error in LoadDBEvent query '%s': %s", query.c_str(), results.ErrorMessage().c_str());
event.id = atoi(row[0]); return false;
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);
} }
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) 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.. //clear out old stuff..
spawn_conditions.clear(); 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);
} std::string query = StringFormat("SELECT id, onchange, value "
mysql_free_result(result); "FROM spawn_conditions "
} else { "WHERE zone = '%s'", zone_name);
LogFile->write(EQEMuLog::Error, "Error in LoadSpawnConditions query '%s': %s", query, errbuf); auto results = database.QueryDatabase(query);
safe_delete_array(query); if (!results.Success()) {
LogFile->write(EQEMuLog::Error, "Error in LoadSpawnConditions query '%s': %s", query.c_str(), results.ErrorMessage().c_str());
return false; 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 //load values
len = MakeAnyLenString(&query, "SELECT id, value FROM spawn_condition_values WHERE zone='%s' and instance_id=%u", zone_name, instance_id); query = StringFormat("SELECT id, value FROM spawn_condition_values "
if (database.RunQuery(query, len, errbuf, &result)) { "WHERE zone = '%s' AND instance_id = %u",
safe_delete_array(query); zone_name, instance_id);
while((row = mysql_fetch_row(result))) results = database.QueryDatabase(query);
{ if (!results.Success()) {
std::map<uint16, SpawnCondition>::iterator iter = spawn_conditions.find(atoi(row[0])); LogFile->write(EQEMuLog::Error, "Error in LoadSpawnConditions query '%s': %s", query.c_str(), results.ErrorMessage().c_str());
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);
spawn_conditions.clear(); spawn_conditions.clear();
return false; 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 //load spawn events
SpawnEvent event; query = StringFormat("SELECT id, cond_id, period, next_minute, next_hour, "
len = MakeAnyLenString(&query, "next_day, next_month, next_year, enabled, action, argument, strict "
"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);
"FROM spawn_events WHERE zone='%s'", zone_name); results = database.QueryDatabase(query);
if (database.RunQuery(query, len, errbuf, &result)) { if (!results.Success()) {
safe_delete_array(query); LogFile->write(EQEMuLog::Error, "Error in LoadSpawnConditions events query '%s': %s", query.c_str(), results.ErrorMessage().c_str());
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);
return false; 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 //now we need to catch up on events that happened while we were away
//and use them to alter just the condition variables. //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; TimeOfDay_Struct tod;
zone->zone_time.getEQTimeOfDay(&tod); zone->zone_time.getEQTimeOfDay(&tod);
std::vector<SpawnEvent>::iterator cur,end; for(auto cur = spawn_events.begin(); cur != spawn_events.end(); ++cur) {
cur = spawn_events.begin();
end = spawn_events.end();
bool ran;
for(; cur != end; ++cur) {
SpawnEvent &cevent = *cur; SpawnEvent &cevent = *cur;
bool StrictCheck = false; bool StrictCheck = false;
if(cevent.strict && if(cevent.strict &&
cevent.next.hour == tod.hour && cevent.next.hour == tod.hour &&
cevent.next.day == tod.day && cevent.next.day == tod.day &&
cevent.next.month == tod.month && cevent.next.month == tod.month &&
cevent.next.year == tod.year) cevent.next.year == tod.year)
StrictCheck = true; StrictCheck = true;
@ -874,43 +842,42 @@ bool SpawnConditionManager::LoadSpawnConditions(const char* zone_name, uint32 in
if(!cevent.enabled || !StrictCheck) if(!cevent.enabled || !StrictCheck)
SetCondition(zone->GetShortName(), zone->GetInstanceID(),cevent.condition_id,0); SetCondition(zone->GetShortName(), zone->GetInstanceID(),cevent.condition_id,0);
if(cevent.enabled) if(!cevent.enabled)
{ continue;
//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.
}
ran = false; //watch for special case of all 0s, which means to reset next to now
while(EQTime::IsTimeBefore(&tod, &cevent.next)) { if(cevent.next.year == 0 && cevent.next.month == 0 && cevent.next.day == 0 && cevent.next.hour == 0 && cevent.next.minute == 0) {
_log(SPAWNS__CONDITIONS, "Catch up triggering on event %d", cevent.id); _log(SPAWNS__CONDITIONS, "Initial next trigger time set for spawn event %d", cevent.id);
//this event has been triggered. memcpy(&cevent.next, &tod, sizeof(cevent.next));
//execute the event //add one period
if(!cevent.strict || StrictCheck) EQTime::AddMinutes(cevent.period, &cevent.next);
ExecEvent(cevent, false); //save it in the db.
UpdateDBEvent(cevent);
//add the period of the event to the trigger time continue; //were done with this event.
EQTime::AddMinutes(cevent.period, &cevent.next); }
ran = true;
} bool ran = false;
//only write it out if the event actually ran while(EQTime::IsTimeBefore(&tod, &cevent.next)) {
if(ran) { _log(SPAWNS__CONDITIONS, "Catch up triggering on event %d", cevent.id);
//save the event in the DB //this event has been triggered.
UpdateDBEvent(cevent); //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. //now our event timers are all up to date, find our closest event.
FindNearestEvent(); FindNearestEvent();
return(true); return true;
} }
void SpawnConditionManager::FindNearestEvent() { void SpawnConditionManager::FindNearestEvent() {
@ -926,7 +893,7 @@ void SpawnConditionManager::FindNearestEvent() {
if(cevent.enabled) if(cevent.enabled)
{ {
//see if this event is before our last nearest //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)); memcpy(&next_event, &cevent.next, sizeof(next_event));
next_id = cevent.id; next_id = cevent.id;
@ -1162,37 +1129,28 @@ int16 SpawnConditionManager::GetCondition(const char *zone_short, uint32 instanc
} }
SpawnCondition &cond = condi->second; SpawnCondition &cond = condi->second;
return(cond.value); 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);
} }
//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) { bool SpawnConditionManager::Check(uint16 condition, int16 min_value) {

View File

@ -140,101 +140,92 @@ bool SpawnGroupList::RemoveSpawnGroup(uint32 in_id) {
} }
bool ZoneDatabase::LoadSpawnGroups(const char* zone_name, uint16 version, SpawnGroupList* spawn_group_list) { 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;
query = 0; std::string query = StringFormat("SELECT DISTINCT(spawngroupID), spawngroup.name, spawngroup.spawn_limit, "
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)) "spawngroup.dist, spawngroup.max_x, spawngroup.min_x, "
{ "spawngroup.max_y, spawngroup.min_y, spawngroup.delay, "
safe_delete_array(query); "spawngroup.despawn, spawngroup.despawn_timer, spawngroup.mindelay "
while((row = mysql_fetch_row(result))) { "FROM spawn2, spawngroup WHERE spawn2.spawngroupID = spawngroup.ID "
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])); "AND spawn2.version = %u and zone = '%s'", version, zone_name);
spawn_group_list->AddSpawnGroup(newSpawnGroup); auto results = QueryDatabase(query);
} if (!results.Success()) {
mysql_free_result(result); _log(ZONE__SPAWNS, "Error2 in PopulateZoneLists query '%s' ", query.c_str());
}
else
{
_log(ZONE__SPAWNS, "Error2 in PopulateZoneLists query '%s' ", query);
safe_delete_array(query);
return false; return false;
} }
query = 0; for (auto row = results.begin(); row != results.end(); ++row) {
if (RunQuery(query, MakeAnyLenString(&query, SpawnGroup* newSpawnGroup = new SpawnGroup(atoi(row[0]), row[1], atoi(row[2]), atof(row[3]),
"SELECT DISTINCT spawnentry.spawngroupID, npcid, chance, " atof(row[4]), atof(row[5]), atof(row[6]), atof(row[7]),
"npc_types.spawn_limit AS sl " atoi(row[8]), atoi(row[9]), atoi(row[10]), atoi(row[11]));
"FROM spawnentry, spawn2, npc_types " spawn_group_list->AddSpawnGroup(newSpawnGroup);
"WHERE spawnentry.npcID=npc_types.id AND spawnentry.spawngroupID=spawn2.spawngroupID " }
"AND zone='%s'", zone_name), errbuf, &result)) {
safe_delete_array(query); query = StringFormat("SELECT DISTINCT spawnentry.spawngroupID, npcid, chance, "
while((row = mysql_fetch_row(result))) "npc_types.spawn_limit AS sl "
{ "FROM spawnentry, spawn2, npc_types "
SpawnEntry* newSpawnEntry = new SpawnEntry( atoi(row[1]), atoi(row[2]), row[3]?atoi(row[3]):0); "WHERE spawnentry.npcID=npc_types.id "
SpawnGroup *sg = spawn_group_list->GetSpawnGroup(atoi(row[0])); "AND spawnentry.spawngroupID = spawn2.spawngroupID "
if (sg) "AND zone = '%s'", zone_name);
sg->AddSpawnEntry(newSpawnEntry); results = QueryDatabase(query);
else if (!results.Success()) {
_log(ZONE__SPAWNS, "Error in LoadSpawnGroups %s ", query); _log(ZONE__SPAWNS, "Error2 in PopulateZoneLists query '%'", query.c_str());
}
mysql_free_result(result);
}
else
{
_log(ZONE__SPAWNS, "Error2 in PopulateZoneLists query '%'", query);
safe_delete_array(query);
return false; 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);
}
return true; return true;
} }
bool ZoneDatabase::LoadSpawnGroupsByID(int spawngroupid, SpawnGroupList* spawn_group_list) { bool ZoneDatabase::LoadSpawnGroupsByID(int spawngroupid, SpawnGroupList* spawn_group_list) {
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
MYSQL_RES *result;
MYSQL_ROW row;
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)) std::string query = StringFormat("SELECT DISTINCT(spawngroup.id), spawngroup.name, spawngroup.spawn_limit, "
{ "spawngroup.dist, spawngroup.max_x, spawngroup.min_x, "
safe_delete_array(query); "spawngroup.max_y, spawngroup.min_y, spawngroup.delay, "
while((row = mysql_fetch_row(result))) { "spawngroup.despawn, spawngroup.despawn_timer, spawngroup.mindelay "
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])); "FROM spawngroup WHERE spawngroup.ID = '%i'", spawngroupid);
spawn_group_list->AddSpawnGroup(newSpawnGroup); auto results = QueryDatabase(query);
} if (!results.Success()) {
mysql_free_result(result); _log(ZONE__SPAWNS, "Error2 in PopulateZoneLists query %s", query.c_str());
} return false;
else }
{
_log(ZONE__SPAWNS, "Error2 in PopulateZoneLists query %s", query); for (auto row = results.begin(); row != results.end(); ++row) {
safe_delete_array(query); 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; return false;
} }
query = 0; for(auto row = results.begin(); row != results.end(); ++row) {
if (RunQuery(query, MakeAnyLenString(&query, SpawnEntry* newSpawnEntry = new SpawnEntry( atoi(row[1]), atoi(row[2]), row[3]?atoi(row[3]):0);
"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)) { SpawnGroup *sg = spawn_group_list->GetSpawnGroup(atoi(row[0]));
safe_delete_array(query); if (!sg) {
while((row = mysql_fetch_row(result))) _log(ZONE__SPAWNS, "Error in SpawngroupID: %s ", row[0]);
{ continue;
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);
sg->AddSpawnEntry(newSpawnEntry); }
else
_log(ZONE__SPAWNS, "Error in SpawngroupID: %s ", row[0]);
}
mysql_free_result(result);
}
else
{
_log(ZONE__SPAWNS, "Error3 in PopulateZoneLists query '%s'", row[0]);
safe_delete_array(query);
return false;
}
return true; return true;
} }

View File

@ -1252,7 +1252,7 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, uint16 slot,
} }
} }
if(IsClient()) { if(IsClient()) {
CheckNumHitsRemaining(NUMHIT_MatchingSpells); CheckNumHitsRemaining(NUMHIT_MatchingSpells);
TrySympatheticProc(target, spell_id); 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); mlog(AA__MESSAGE, "Project Illusion overwrote target caster: %s spell id: %d was ON", GetName(), spell_id);
targetType = ST_GroupClientAndPet; targetType = ST_GroupClientAndPet;
} }
if (spell_target && !spell_target->PassCastRestriction(true, spells[spell_id].CastRestriction)){ if (spell_target && !spell_target->PassCastRestriction(true, spells[spell_id].CastRestriction)){
Message_StringID(13,SPELL_NEED_TAR); Message_StringID(13,SPELL_NEED_TAR);
return false; 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) //Must be out of combat. (If Beneficial checks casters combat state, Deterimental checks targets)
if (!spells[spell_id].InCombat && spells[spell_id].OutofCombat){ if (!spells[spell_id].InCombat && spells[spell_id].OutofCombat){
if (IsDetrimentalSpell(spell_id)) { if (IsDetrimentalSpell(spell_id)) {
if ( (spell_target->IsNPC() && spell_target->IsEngaged()) || if ( (spell_target->IsNPC() && spell_target->IsEngaged()) ||
(spell_target->IsClient() && spell_target->CastToClient()->GetAggroCount())){ (spell_target->IsClient() && spell_target->CastToClient()->GetAggroCount())){
Message_StringID(13,SPELL_NO_EFFECT); //Unsure correct string Message_StringID(13,SPELL_NO_EFFECT); //Unsure correct string
return false; return false;
} }
} }
else if (IsBeneficialSpell(spell_id)) { else if (IsBeneficialSpell(spell_id)) {
if ( (IsNPC() && IsEngaged()) || if ( (IsNPC() && IsEngaged()) ||
(IsClient() && CastToClient()->GetAggroCount())){ (IsClient() && CastToClient()->GetAggroCount())){
if (IsDiscipline(spell_id)) if (IsDiscipline(spell_id))
Message_StringID(13,NO_ABILITY_IN_COMBAT); 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) //Must be in combat. (If Beneficial checks casters combat state, Deterimental checks targets)
else if (spells[spell_id].InCombat && !spells[spell_id].OutofCombat){ else if (spells[spell_id].InCombat && !spells[spell_id].OutofCombat){
if (IsDetrimentalSpell(spell_id)) { if (IsDetrimentalSpell(spell_id)) {
if ( (spell_target->IsNPC() && !spell_target->IsEngaged()) || if ( (spell_target->IsNPC() && !spell_target->IsEngaged()) ||
(spell_target->IsClient() && !spell_target->CastToClient()->GetAggroCount())){ (spell_target->IsClient() && !spell_target->CastToClient()->GetAggroCount())){
Message_StringID(13,SPELL_NO_EFFECT); //Unsure correct string Message_StringID(13,SPELL_NO_EFFECT); //Unsure correct string
return false; return false;
} }
} }
else if (IsBeneficialSpell(spell_id)) { else if (IsBeneficialSpell(spell_id)) {
if ( (IsNPC() && !IsEngaged()) || if ( (IsNPC() && !IsEngaged()) ||
(IsClient() && !CastToClient()->GetAggroCount())){ (IsClient() && !CastToClient()->GetAggroCount())){
if (IsDiscipline(spell_id)) if (IsDiscipline(spell_id))
Message_StringID(13,NO_ABILITY_OUT_OF_COMBAT); 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: case ST_PetMaster:
{ {
Mob *owner = nullptr; Mob *owner = nullptr;
if (IsPet()) if (IsPet())
owner = GetOwner(); owner = GetOwner();
else if ((IsNPC() && CastToNPC()->GetSwarmOwner())) else if ((IsNPC() && CastToNPC()->GetSwarmOwner()))
owner = entity_list.GetMobID(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)) if (!TrySpellProjectile(spell_target, spell_id))
return false; return false;
} }
else if(!SpellOnTarget(spell_id, spell_target, false, true, resist_adjust, false)) { else if(!SpellOnTarget(spell_id, spell_target, false, true, resist_adjust, false)) {
if(IsBuffSpell(spell_id) && IsBeneficialSpell(spell_id)) { if(IsBuffSpell(spell_id) && IsBeneficialSpell(spell_id)) {
// Prevent mana usage/timers being set for beneficial buffs // 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))) if ((effect2 == SE_BStacker) && (!IsCastonFadeDurationSpell(spellid1) && buffs[buffslot].ticsremaining != 1 && IsEffectInSpell(spellid1, SE_CStacker)))
return -1; return -1;
} }
if (spellbonuses.DStacker[0]) { if (spellbonuses.DStacker[0]) {
if ((effect2 == SE_DStacker) && (sp2.effectid[i] <= spellbonuses.DStacker[1])) if ((effect2 == SE_DStacker) && (sp2.effectid[i] <= spellbonuses.DStacker[1]))
return -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.", 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); sp1.name, spellid1, blocked_effect, blocked_slot, blocked_below_value);
} }
} }
} }
} else { } else {
mlog(SPELLS__STACKING, "%s (%d) and %s (%d) appear to be in the same line, skipping Stacking Overwrite/Blocking checks", 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) ) if(spell_effectiveness == 0 || !IsPartialCapableSpell(spell_id) )
{ {
mlog(SPELLS__RESISTS, "Spell %d was completely resisted by %s", spell_id, spelltar->GetName()); mlog(SPELLS__RESISTS, "Spell %d was completely resisted by %s", spell_id, spelltar->GetName());
if (spells[spell_id].resisttype == RESIST_PHYSICAL){ if (spells[spell_id].resisttype == RESIST_PHYSICAL){
Message_StringID(MT_SpellFailure, PHYSICAL_RESIST_FAIL,spells[spell_id].name); Message_StringID(MT_SpellFailure, PHYSICAL_RESIST_FAIL,spells[spell_id].name);
spelltar->Message_StringID(MT_SpellFailure, YOU_RESIST, 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); TrySpellTrigger(spelltar, spell_id);
TryApplyEffect(spelltar, spell_id); TryApplyEffect(spelltar, spell_id);
if (spelltar->IsAIControlled() && IsDetrimentalSpell(spell_id) && !IsHarmonySpell(spell_id)) { if (spelltar->IsAIControlled() && IsDetrimentalSpell(spell_id) && !IsHarmonySpell(spell_id)) {
int32 aggro_amount = CheckAggroAmount(spell_id, isproc); int32 aggro_amount = CheckAggroAmount(spell_id, isproc);
mlog(SPELLS__CASTING, "Spell %d cast on %s generated %d hate", spell_id, spelltar->GetName(), aggro_amount); 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); safe_delete(action_packet);
return false; return false;
} }
// cause the effects to the target // cause the effects to the target
if(!spelltar->SpellEffect(this, spell_id, spell_effectiveness)) 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; return false;
} }
if (IsDetrimentalSpell(spell_id)) { if (IsDetrimentalSpell(spell_id)) {
CheckNumHitsRemaining(NUMHIT_OutgoingSpells); CheckNumHitsRemaining(NUMHIT_OutgoingSpells);
if (spelltar) if (spelltar)
spelltar->CheckNumHitsRemaining(NUMHIT_IncomingSpells); spelltar->CheckNumHitsRemaining(NUMHIT_IncomingSpells);
} }
@ -4408,7 +4408,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
if (CharismaCheck) 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 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. 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. 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) { if (CharmTick) {
int min_charmbreakchance = ((100/RuleI(Spells, CharmBreakCheckChance))/66 * 100)*2; int min_charmbreakchance = ((100/RuleI(Spells, CharmBreakCheckChance))/66 * 100)*2;
if (resist_chance < min_charmbreakchance) if (resist_chance < min_charmbreakchance)
resist_chance = min_charmbreakchance; resist_chance = min_charmbreakchance;
} }
@ -4965,68 +4965,56 @@ int Client::FindSpellBookSlotBySpellID(uint16 spellid) {
return -1; //default 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; std::string spell_Global_Name;
int Spell_Global_Value; int spell_Global_Value;
int Global_Value; int global_Value;
char errbuf[MYSQL_ERRMSG_SIZE]; std::string query = StringFormat("SELECT qglobal, value FROM spell_globals "
char *query = 0; "WHERE spellid = %i", spell_ID);
MYSQL_RES *result; auto results = database.QueryDatabase(query);
MYSQL_ROW row; 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());
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);
return false; // Query failed, so prevent spell from scribing just in case 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 // TODO get rid of this
@ -5081,7 +5069,7 @@ bool Mob::FindType(uint16 type, bool bOffensive, uint16 threshold) {
} }
bool Mob::IsCombatProc(uint16 spell_id) { bool Mob::IsCombatProc(uint16 spell_id) {
if (RuleB(Spells, FocusCombatProcs)) if (RuleB(Spells, FocusCombatProcs))
return false; return false;
@ -5092,7 +5080,7 @@ bool Mob::IsCombatProc(uint16 spell_id) {
{ {
for (int i = 0; i < MAX_PROCS; i++){ 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){ || RangedProcs[i].spellID == spell_id){
return true; return true;
} }
@ -5150,7 +5138,7 @@ bool Mob::AddDefensiveProc(uint16 spell_id, uint16 iChance, uint16 base_spell_id
{ {
if(spell_id == SPELL_UNKNOWN) if(spell_id == SPELL_UNKNOWN)
return(false); return(false);
int i; int i;
for (i = 0; i < MAX_PROCS; i++) { for (i = 0; i < MAX_PROCS; i++) {
if (DefensiveProcs[i].spellID == SPELL_UNKNOWN) { if (DefensiveProcs[i].spellID == SPELL_UNKNOWN) {

View File

@ -22,7 +22,10 @@
#include "../common/rulesys.h" #include "../common/rulesys.h"
#include "quest_parser_collection.h" #include "quest_parser_collection.h"
#include "worldserver.h" #include "worldserver.h"
#include "queryserv.h"
extern WorldServer worldserver; extern WorldServer worldserver;
extern QueryServ* QServ;
// The maximum amount of a single bazaar/barter transaction expressed in copper. // The maximum amount of a single bazaar/barter transaction expressed in copper.
// Equivalent to 2 Million plat // 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) // 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) { void Trade::AddEntity(uint16 trade_slot_id, uint32 stack_size) {
// TODO: review for inventory saves // TODO: review for inventory saves / consider changing return type to bool so failure can be passed to desync handler
if (!owner || !owner->IsClient()) { if (!owner || !owner->IsClient()) {
// This should never happen // 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) if (_stack_size > 0)
inst->SetCharges(_stack_size); inst->SetCharges(_stack_size);
else else
client->DeleteItemInInventory(from_slot_id); client->DeleteItemInInventory(MainCursor);
SendItemData(inst2, trade_slot_id); 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); _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->PutItemInInventory(trade_slot_id, *inst);
client->DeleteItemInInventory(from_slot_id); client->DeleteItemInInventory(MainCursor);
} }
} }
@ -316,200 +319,464 @@ void Trade::DumpTrade()
#endif #endif
void Client::ResetTrade() { void Client::ResetTrade() {
const Item_Struct* TempItem = 0;
ItemInst* ins;
int x;
AddMoneyToPP(trade->cp, trade->sp, trade->gp, trade->pp, true); AddMoneyToPP(trade->cp, trade->sp, trade->gp, trade->pp, true);
for(x = EmuConstants::TRADE_BEGIN; x <= EmuConstants::TRADE_END; x++)
{ // step 1: process bags
TempItem = 0; for (int16 trade_slot = EmuConstants::TRADE_BEGIN; trade_slot <= EmuConstants::TRADE_END; ++trade_slot) {
ins = GetInv().GetItem(x); const ItemInst* inst = m_inv[trade_slot];
if (ins)
TempItem = ins->GetItem(); if (inst && inst->IsType(ItemClassContainer)) {
if (TempItem) int16 free_slot = m_inv.FindFreeSlotForTradeItem(inst);
{
bool is_arrow = (TempItem->ItemType == ItemTypeArrow) ? true : false; if (free_slot != INVALID_INDEX) {
int freeslotid = GetInv().FindFreeSlot(ins->IsType(ItemClassContainer), true, TempItem->Size, is_arrow); PutItemInInventory(free_slot, *inst);
if (freeslotid == INVALID_INDEX) SendItemPacket(free_slot, inst, ItemPacketTrade);
{
DropInst(ins);
} }
else else {
{ DropInst(inst);
PutItemInInventory(freeslotid, *ins);
SendItemPacket(freeslotid, ins, ItemPacketTrade);
} }
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<void*>* event_details) {
if(tradingWith && tradingWith->IsClient()) { if(tradingWith && tradingWith->IsClient()) {
Client* other = tradingWith->CastToClient(); Client* other = tradingWith->CastToClient();
QSPlayerLogTrade_Struct* qs_audit = nullptr;
bool qs_log = false;
if(other) { if(other) {
mlog(TRADING__CLIENT, "Finishing trade with client %s", other->GetName()); mlog(TRADING__CLIENT, "Finishing trade with client %s", other->GetName());
int16 slot_id; this->AddMoneyToPP(other->trade->cp, other->trade->sp, other->trade->gp, other->trade->pp, true);
const Item_Struct* item = nullptr;
QSPlayerLogTrade_Struct* qsaudit = nullptr; // step 0: pre-processing
bool QSPLT = false;
// QS code // QS code
if(qspack && RuleB(QueryServ, PlayerLogTrades)) { if (RuleB(QueryServ, PlayerLogTrades) && event_entry && event_details) {
qsaudit = (QSPlayerLogTrade_Struct*) qspack->pBuffer; qs_audit = (QSPlayerLogTrade_Struct*)event_entry;
QSPLT = true; qs_log = true;
if (finalizer) {
qs_audit->char2_id = this->character_id;
if(finalizer) { qsaudit->char2_id = this->character_id; } qs_audit->char2_money.platinum = this->trade->pp;
else { qsaudit->char1_id = this->character_id; } 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 // step 1: process bags
for(int16 i = EmuConstants::TRADE_BEGIN; i <= EmuConstants::TRADE_END; i++){ for (int16 trade_slot = EmuConstants::TRADE_BEGIN; trade_slot <= EmuConstants::TRADE_END; ++trade_slot) {
const ItemInst* inst = m_inv[i]; const ItemInst* inst = m_inv[trade_slot];
uint16 parent_offset = 0;
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 (free_slot != INVALID_INDEX) {
if(QSPLT) { if (other->PutItemInInventory(free_slot, *inst, true)) {
uint16 item_count = qsaudit->char1_count + qsaudit->char2_count; mlog(TRADING__CLIENT, "Container %s (%d) successfully transferred, deleting from trade slot.", inst->GetItem()->Name, inst->GetItem()->ID);
parent_offset = item_count; if (qs_log) {
QSTradeItems_Struct* detail = new QSTradeItems_Struct;
qsaudit->items[item_count].from_id = this->character_id; detail->from_id = this->character_id;
qsaudit->items[item_count].from_slot = i; detail->from_slot = trade_slot;
qsaudit->items[item_count].to_id = other->CharacterID(); detail->to_id = other->CharacterID();
qsaudit->items[item_count].to_slot = 0; detail->to_slot = free_slot;
qsaudit->items[item_count].item_id = inst->GetID(); detail->item_id = inst->GetID();
qsaudit->items[item_count].charges = inst->GetCharges(); detail->charges = 1;
qsaudit->items[item_count].aug_1 = inst->GetAugmentItemID(1); detail->aug_1 = inst->GetAugmentItemID(1);
qsaudit->items[item_count].aug_2 = inst->GetAugmentItemID(2); detail->aug_2 = inst->GetAugmentItemID(2);
qsaudit->items[item_count].aug_3 = inst->GetAugmentItemID(3); detail->aug_3 = inst->GetAugmentItemID(3);
qsaudit->items[item_count].aug_4 = inst->GetAugmentItemID(4); detail->aug_4 = inst->GetAugmentItemID(4);
qsaudit->items[item_count].aug_5 = inst->GetAugmentItemID(5); detail->aug_5 = inst->GetAugmentItemID(5);
if(finalizer) { qsaudit->char2_count++; } event_details->push_back(detail);
else { qsaudit->char1_count++; }
if (finalizer)
qs_audit->char2_count += detail->charges;
else
qs_audit->char1_count += detail->charges;
if(inst->IsType(ItemClassContainer)) { //for (uint8 sub_slot = SUB_BEGIN; ((sub_slot < inst->GetItem()->BagSlots) && (sub_slot < EmuConstants::ITEM_CONTAINER_SIZE)); ++sub_slot) {
// Pseudo-Slot ID's are generated based on how the db saves bag items... for (uint8 sub_slot = SUB_BEGIN; (sub_slot < EmuConstants::ITEM_CONTAINER_SIZE); ++sub_slot) { // this is to catch ALL items
for(uint8 j = SUB_BEGIN; j < inst->GetItem()->BagSlots; j++) { const ItemInst* bag_inst = inst->GetItem(sub_slot);
const ItemInst* baginst = inst->GetItem(j);
if(baginst == nullptr) { continue; } if (bag_inst) {
detail = new QSTradeItems_Struct;
int16 k=Inventory::CalcSlotId(i, j); detail->from_id = this->character_id;
item_count = qsaudit->char1_count + qsaudit->char2_count; 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; event_details->push_back(detail);
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);
if(finalizer) { qsaudit->char2_count++; } if (finalizer)
else { qsaudit->char1_count++; } qs_audit->char2_count += detail->charges;
} else
} qs_audit->char1_count += detail->charges;
} }
}
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;
} }
} }
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 { else {
mlog(TRADING__ERROR, "Container %s (%d) is NoDrop, returning to giver.", inst->GetItem()->Name, inst->GetItem()->ID);
PushItemOnCursor(*inst, true); 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) { // step 2a: process stackables
qsaudit->items[parent_offset].to_id = this->character_id; for (int16 trade_slot = EmuConstants::TRADE_BEGIN; trade_slot <= EmuConstants::TRADE_END; ++trade_slot) {
qsaudit->items[parent_offset].to_slot = MainCursor; ItemInst* inst = GetInv().GetItem(trade_slot);
if(inst->IsType(ItemClassContainer)) { if (inst && inst->IsStackable()) {
for(uint8 bagslot_idx = SUB_BEGIN; bagslot_idx < inst->GetItem()->BagSlots; bagslot_idx++) { while (true) {
const ItemInst* bag_inst = inst->GetItem(bagslot_idx); // 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; } if ((partial_slot == MainCursor) || (partial_slot == INVALID_INDEX))
int16 to_bagslot_id = Inventory::CalcSlotId(MainCursor, bagslot_idx); break;
qsaudit->items[++parent_offset].to_id = this->character_id; ItemInst* partial_inst = other->GetInv().GetItem(partial_slot);
qsaudit->items[parent_offset].to_slot = to_bagslot_id;
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 // step 2b: adjust trade stack bias
this->AddMoneyToPP(other->trade->cp, other->trade->sp, other->trade->gp, other->trade->pp, true); // (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 (inst && inst->IsStackable()) {
if(QSPLT) { for (int16 bias_slot = EmuConstants::TRADE_BEGIN; bias_slot <= EmuConstants::TRADE_END; ++bias_slot) {
if(finalizer) { if (bias_slot >= trade_slot)
qsaudit->char2_money.platinum = this->trade->pp; break;
qsaudit->char2_money.gold = this->trade->gp;
qsaudit->char2_money.silver = this->trade->sp; ItemInst* bias_inst = GetInv().GetItem(bias_slot);
qsaudit->char2_money.copper = this->trade->cp;
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; // step 3: process everything else
qsaudit->char1_money.silver = this->trade->sp; for (int16 trade_slot = EmuConstants::TRADE_BEGIN; trade_slot <= EmuConstants::TRADE_END; ++trade_slot) {
qsaudit->char1_money.copper = this->trade->cp; 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()) { else if(tradingWith && tradingWith->IsNPC()) {
QSPlayerLogHandin_Struct* qsaudit = nullptr; QSPlayerLogHandin_Struct* qs_audit = nullptr;
bool QSPLH = false; bool qs_log = false;
// QS code // QS code
if(qspack && RuleB(QueryServ, PlayerLogTrades)) { if(RuleB(QueryServ, PlayerLogTrades) && event_entry && event_details) {
// Currently provides only basic functionality. Calling method will also // Currently provides only basic functionality. Calling method will also
// need to be modified before item returns and rewards can be logged. -U // need to be modified before item returns and rewards can be logged. -U
qsaudit = (QSPlayerLogHandin_Struct*) qspack->pBuffer; qs_audit = (QSPlayerLogHandin_Struct*)event_entry;
QSPLH = true; qs_log = true;
qsaudit->quest_id = 0; qs_audit->quest_id = 0;
qsaudit->char_id = character_id; qs_audit->char_id = character_id;
qsaudit->char_money.platinum = trade->pp; qs_audit->char_money.platinum = trade->pp;
qsaudit->char_money.gold = trade->gp; qs_audit->char_money.gold = trade->gp;
qsaudit->char_money.silver = trade->sp; qs_audit->char_money.silver = trade->sp;
qsaudit->char_money.copper = trade->cp; qs_audit->char_money.copper = trade->cp;
qsaudit->char_count = 0; qs_audit->char_count = 0;
qsaudit->npc_id = tradingWith->GetNPCTypeID(); qs_audit->npc_id = tradingWith->GetNPCTypeID();
qsaudit->npc_money.platinum = 0; qs_audit->npc_money.platinum = 0;
qsaudit->npc_money.gold = 0; qs_audit->npc_money.gold = 0;
qsaudit->npc_money.silver = 0; qs_audit->npc_money.silver = 0;
qsaudit->npc_money.copper = 0; qs_audit->npc_money.copper = 0;
qsaudit->npc_count = 0; qs_audit->npc_count = 0;
} }
if(QSPLH) { // This can be incoporated below when revisions are made -U if(qs_log) { // This can be incorporated below when revisions are made -U
for(int16 slot_id = EmuConstants::TRADE_BEGIN; slot_id <= EmuConstants::TRADE_NPC_END; slot_id++) { for (int16 trade_slot = EmuConstants::TRADE_BEGIN; trade_slot <= EmuConstants::TRADE_NPC_END; ++trade_slot) {
const ItemInst* trade_inst = m_inv[slot_id]; const ItemInst* trade_inst = m_inv[trade_slot];
if(trade_inst) { 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; strcpy(detail->action_type, "HANDIN");
qsaudit->items[qsaudit->char_count].item_id = trade_inst->GetID();
qsaudit->items[qsaudit->char_count].charges = trade_inst->GetCharges(); detail->char_slot = trade_slot;
qsaudit->items[qsaudit->char_count].aug_1 = trade_inst->GetAugmentItemID(1); detail->item_id = trade_inst->GetID();
qsaudit->items[qsaudit->char_count].aug_2 = trade_inst->GetAugmentItemID(2); detail->charges = (!trade_inst->IsStackable() ? 1 : trade_inst->GetCharges());
qsaudit->items[qsaudit->char_count].aug_3 = trade_inst->GetAugmentItemID(3); detail->aug_1 = trade_inst->GetAugmentItemID(1);
qsaudit->items[qsaudit->char_count].aug_4 = trade_inst->GetAugmentItemID(4); detail->aug_2 = trade_inst->GetAugmentItemID(2);
qsaudit->items[qsaudit->char_count++].aug_5 = trade_inst->GetAugmentItemID(5); 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)) { if(trade_inst->IsType(ItemClassContainer)) {
for(uint8 bag_idx = SUB_BEGIN; bag_idx < trade_inst->GetItem()->BagSlots; bag_idx++) { for (uint8 sub_slot = SUB_BEGIN; sub_slot < trade_inst->GetItem()->BagSlots; ++sub_slot) {
const ItemInst* trade_baginst = trade_inst->GetItem(bag_idx); const ItemInst* trade_baginst = trade_inst->GetItem(sub_slot);
if(trade_baginst) { 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); strcpy(detail->action_type, "HANDIN");
qsaudit->items[qsaudit->char_count].item_id = trade_baginst->GetID();
qsaudit->items[qsaudit->char_count].charges = trade_baginst->GetCharges(); detail->char_slot = Inventory::CalcSlotId(trade_slot, sub_slot);
qsaudit->items[qsaudit->char_count].aug_1 = trade_baginst->GetAugmentItemID(1); detail->item_id = trade_baginst->GetID();
qsaudit->items[qsaudit->char_count].aug_2 = trade_baginst->GetAugmentItemID(2); detail->charges = (!trade_inst->IsStackable() ? 1 : trade_inst->GetCharges());
qsaudit->items[qsaudit->char_count].aug_3 = trade_baginst->GetAugmentItemID(3); detail->aug_1 = trade_baginst->GetAugmentItemID(1);
qsaudit->items[qsaudit->char_count].aug_4 = trade_baginst->GetAugmentItemID(4); detail->aug_2 = trade_baginst->GetAugmentItemID(2);
qsaudit->items[qsaudit->char_count++].aug_5 = trade_baginst->GetAugmentItemID(5); 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;
} }
} }
} }

View File

@ -869,97 +869,78 @@ void NPC::AssignWaypoints(int32 grid) {
return; return;
} }
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
MYSQL_RES *result;
MYSQL_ROW row;
bool GridErr = false, WPErr = false;
Waypoints.clear(); Waypoints.clear();
roamer = false;
// Retrieve the wander and pause types for this grid // 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)) std::string query = StringFormat("SELECT `type`, `type2` FROM `grid` WHERE `id` = %i AND `zoneid` = %i", grid, zone->GetZoneID());
{ auto results = database.QueryDatabase(query);
if((row = mysql_fetch_row(result))) 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());
if(row[0] != 0) return;
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);
} }
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) if (results.RowCount() == 0)
{ return;
this->CastToNPC()->SetGrid(grid); // Assign grid number
// Retrieve all waypoints for this grid auto row = results.begin();
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
while((row = mysql_fetch_row(result))) wandertype = atoi(row[0]);
{ pausetype = atoi(row[1]);
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]);
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)) // Retrieve all waypoints for this grid
newwp.z = newz + 1; 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) { if(Waypoints.size() < 2) {
roamer = false; 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) { 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) { 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) { bool ZoneDatabase::GetWaypoints(uint32 grid, uint16 zoneid, uint32 num, wplist* wp) {
char *query = 0;
char errbuff[MYSQL_ERRMSG_SIZE]; if (wp == nullptr)
MYSQL_RES *result; return false;
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)) { std::string query = StringFormat("SELECT x, y, z, pause, heading FROM grid_entries "
safe_delete_array(query); "WHERE gridid = %i AND number = %i AND zoneid = %i", grid, num, zoneid);
if (mysql_num_rows(result) == 1) { auto results = QueryDatabase(query);
row = mysql_fetch_row(result); if (!results.Success()) {
if ( wp ) { LogFile->write(EQEMuLog::Error, "Error in GetWaypoints query '%s': %s", query.c_str(), results.ErrorMessage().c_str());
wp->x = atof( row[0] ); return false;
wp->y = atof( row[1] ); }
wp->z = atof( row[2] );
wp->pause = atoi( row[3] ); if (results.RowCount() != 1)
wp->heading = atof( row[4] ); return false;
}
mysql_free_result(result); auto row = results.begin();
return true;
} wp->x = atof(row[0]);
mysql_free_result(result); wp->y = atof(row[1]);
} wp->z = atof(row[2]);
else { wp->pause = atoi(row[3]);
LogFile->write(EQEMuLog::Error, "Error in GetWaypoints query '%s': %s", query, errbuff); wp->heading = atof(row[4]);
safe_delete_array(query);
} return true;
return false;
} }
void ZoneDatabase::AssignGrid(Client *client, float x, float y, uint32 grid) 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; int matches = 0, fuzzy = 0, spawn2id = 0;
uint32 affected_rows;
float dbx = 0, dby = 0; float dbx = 0, dby = 0;
// looks like most of the stuff in spawn2 is straight integers // looks like most of the stuff in spawn2 is straight integers
// so let's try that first // so let's try that first
if(!RunQuery( std::string query = StringFormat("SELECT id, x, y FROM spawn2 WHERE zone = '%s' AND x = %i AND y = %i",
query, zone->GetShortName(), (int)x, (int)y);
MakeAnyLenString( auto results = QueryDatabase(query);
&query, if(!results.Success()) {
"SELECT id,x,y FROM spawn2 WHERE zone='%s' AND x=%i AND y=%i", LogFile->write(EQEMuLog::Error, "Error querying spawn2 '%s': '%s'", query.c_str(), results.ErrorMessage().c_str());
zone->GetShortName(), (int)x, (int)y return;
),
errbuf,
&result
)) {
LogFile->write(EQEMuLog::Error, "Error querying spawn2 '%s': '%s'", query, errbuf);
return;
} }
safe_delete_array(query);
// how much it's allowed to be off by // how much it's allowed to be off by
#define _GASSIGN_TOLERANCE 1.0 #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); query = StringFormat("SELECT id,x,y FROM spawn2 WHERE zone='%s' AND "
if(!RunQuery( "ABS( ABS(x) - ABS(%f) ) < %f AND "
query, "ABS( ABS(y) - ABS(%f) ) < %f",
MakeAnyLenString( zone->GetShortName(), x, _GASSIGN_TOLERANCE, y, _GASSIGN_TOLERANCE);
&query, results = QueryDatabase(query);
"SELECT id,x,y FROM spawn2 WHERE zone='%s' AND " if(!results.Success()) {
"ABS( ABS(x) - ABS(%f) ) < %f AND " LogFile->write(EQEMuLog::Error, "Error querying fuzzy spawn2 '%s': '%s'", query.c_str(), results.ErrorMessage().c_str());
"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);
return; return;
} }
safe_delete_array(query);
fuzzy = 1; fuzzy = 1;
if(!(matches = mysql_num_rows(result))) matches = results.RowCount();
mysql_free_result(result);
} }
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");
{ return;
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);
}
}
} }
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) * 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 * 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 (!remove)
{ {
if(!RunQuery(query, MakeAnyLenString(&query,"INSERT INTO grid(id,zoneid,type,type2) VALUES(%i,%i,%i,%i)",id,zoneid,type,type2), errbuf)) { std::string query = StringFormat("INSERT INTO grid(id, zoneid, type, type2) "
LogFile->write(EQEMuLog::Error, "Error creating grid entry '%s': '%s'", query, errbuf); "VALUES (%i, %i, %i, %i)", id, zoneid, type, type2);
} else { auto results = QueryDatabase(query);
if(c) c->LogSQL(query); if (!results.Success()) {
} LogFile->write(EQEMuLog::Error, "Error creating grid entry '%s': '%s'", query.c_str(), results.ErrorMessage().c_str());
safe_delete_array(query); return;
}
if(client)
client->LogSQL(query.c_str());
return;
} }
else
{ std::string query = StringFormat("DELETE FROM grid where id=%i", id);
if(!RunQuery(query, MakeAnyLenString(&query,"DELETE FROM grid where id=%i",id), errbuf)) { auto results = QueryDatabase(query);
LogFile->write(EQEMuLog::Error, "Error deleting grid '%s': '%s'", query, errbuf); if (!results.Success())
} else { LogFile->write(EQEMuLog::Error, "Error deleting grid '%s': '%s'", query.c_str(), results.ErrorMessage().c_str());
if(c) c->LogSQL(query); else if(client)
} client->LogSQL(query.c_str());
safe_delete_array(query);
query = 0; query = StringFormat("DELETE FROM grid_entries WHERE zoneid = %i AND gridid = %i", zoneid, id);
if(!RunQuery(query, MakeAnyLenString(&query,"DELETE FROM grid_entries WHERE zoneid=%i AND gridid=%i",zoneid,id), errbuf)) { results = QueryDatabase(query);
LogFile->write(EQEMuLog::Error, "Error deleting grid entries '%s': '%s'", query, errbuf); if(!results.Success())
} else { LogFile->write(EQEMuLog::Error, "Error deleting grid entries '%s': '%s'", query.c_str(), results.ErrorMessage().c_str());
if(c) c->LogSQL(query); else if(client)
} client->LogSQL(query.c_str());
safe_delete_array(query);
} }
} /*** END ZoneDatabase::ModifyGrid() ***/
/************************************** /**************************************
* AddWP - Adds a new waypoint to a specific grid for a specific zone. * AddWP - Adds a new waypoint to a specific grid for a specific zone.
*/ */
void ZoneDatabase::AddWP(Client *client, uint32 gridid, uint32 wpnum, float xpos, float ypos, float zpos, uint32 pause, uint16 zoneid, float heading)
void ZoneDatabase::AddWP(Client *c, uint32 gridid, uint32 wpnum, float xpos, float ypos, float zpos, uint32 pause, uint16 zoneid, float heading)
{ {
char *query = 0; std::string query = StringFormat("INSERT INTO grid_entries (gridid, zoneid, `number`, x, y, z, pause, heading) "
char errbuf[MYSQL_ERRMSG_SIZE]; "VALUES (%i, %i, %i, %f, %f, %f, %i, %f)",
gridid, zoneid, wpnum, xpos, ypos, zpos, pause, heading);
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)) { auto results = QueryDatabase(query);
LogFile->write(EQEMuLog::Error, "Error adding waypoint '%s': '%s'", query, errbuf); if (!results.Success()) {
} else { LogFile->write(EQEMuLog::Error, "Error adding waypoint '%s': '%s'", query.c_str(), results.ErrorMessage().c_str());
if(c) c->LogSQL(query); 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 * wp_num: The number of the waypoint being deleted
* zoneid: The ID number of the zone that contains the waypoint being deleted * zoneid: The ID number of the zone that contains the waypoint being deleted
*/ */
void ZoneDatabase::DeleteWaypoint(Client *client, uint32 grid_num, uint32 wp_num, uint16 zoneid)
void ZoneDatabase::DeleteWaypoint(Client *c, uint32 grid_num, uint32 wp_num, uint16 zoneid)
{ {
char *query=0; std::string query = StringFormat("DELETE FROM grid_entries WHERE "
char errbuf[MYSQL_ERRMSG_SIZE]; "gridid = %i AND zoneid = %i AND `number` = %i",
grid_num, zoneid, wp_num);
if(!RunQuery(query, MakeAnyLenString(&query,"DELETE FROM grid_entries where gridid=%i and zoneid=%i and `number`=%i",grid_num,zoneid,wp_num), errbuf)) { auto results = QueryDatabase(query);
LogFile->write(EQEMuLog::Error, "Error deleting waypoint '%s': '%s'", query, errbuf); if(!results.Success()) {
} else { LogFile->write(EQEMuLog::Error, "Error deleting waypoint '%s': '%s'", query.c_str(), results.ErrorMessage().c_str());
if(c) c->LogSQL(query); 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 * 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. * 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) { 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)
char *query = 0; uint32 next_wp_num; // The waypoint number we should be assigning to the new waypoint
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) bool createdNewGrid; // Did we create a new grid in this function?
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];
// See what grid number our spawn is assigned // See what grid number our spawn is assigned
if(RunQuery(query, MakeAnyLenString(&query,"SELECT pathgrid FROM spawn2 WHERE id=%i",spawn2id),errbuf,&result)) std::string query = StringFormat("SELECT pathgrid FROM spawn2 WHERE id = %i", spawn2id);
{ auto results = QueryDatabase(query);
safe_delete_array(query); if (!results.Success()) {
if(mysql_num_rows(result) > 0) // Query error
{ LogFile->write(EQEMuLog::Error, "Error setting pathgrid '%s': '%s'", query.c_str(), results.ErrorMessage().c_str());
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);
return 0; 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 if (results.RowCount() == 0)
{ return 0;
CreatedNewGrid = true;
if((grid_num = GetFreeGrid(zoneid)) == 0) // There are no grids for the current zone -- create Grid #1
grid_num = 1;
if(!RunQuery(query, MakeAnyLenString(&query,"insert into grid set id='%i',zoneid= %i, type='%i', type2='%i'",grid_num,zoneid,type1,type2), errbuf)) { auto row = results.begin();
LogFile->write(EQEMuLog::Error, "Error adding grid '%s': '%s'", query, errbuf); grid_num = atoi(row[0]);
} else {
if(c) c->LogSQL(query);
}
safe_delete_array(query);
query = 0; if (grid_num == 0)
if(!RunQuery(query, MakeAnyLenString(&query,"update spawn2 set pathgrid='%i' where id='%i'",grid_num,spawn2id), errbuf)) { { // Our spawn doesn't have a grid assigned to it -- we need to create a new grid and assign it to the spawn
LogFile->write(EQEMuLog::Error, "Error updating spawn2 pathing '%s': '%s'", query, errbuf); createdNewGrid = true;
} else { grid_num = GetFreeGrid(zoneid);
if(c) c->LogSQL(query); if(grid_num == 0) // There are no grids for the current zone -- create Grid #1
} grid_num = 1;
safe_delete_array(query);
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 else // NPC had a grid assigned to it
CreatedNewGrid = false; createdNewGrid = false;
// Find out what the next waypoint is for this grid // Find out what the next waypoint is for this grid
query = 0; query = StringFormat("SELECT max(`number`) FROM grid_entries WHERE zoneid = '%i' AND gridid = '%i'", zoneid, grid_num);
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;
mysql_free_result(result); if(!results.Success()) { // Query error
} LogFile->write(EQEMuLog::Error, "Error getting next waypoint id '%s': '%s'", query.c_str(), results.ErrorMessage().c_str());
else { // Query error
LogFile->write(EQEMuLog::Error, "Error getting next waypoint id '%s': '%s'", query, errbuf);
return 0; return 0;
} }
query = 0; row = results.begin();
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)) { if(row[0] != 0)
LogFile->write(EQEMuLog::Error, "Error adding grid entry '%s': '%s'", query, errbuf); next_wp_num = atoi(row[0]) + 1;
} else { else // No waypoints in this grid yet
if(c) c->LogSQL(query); next_wp_num = 1;
}
safe_delete_array(query);
if(CreatedNewGrid) query = StringFormat("INSERT INTO grid_entries(gridid, zoneid, `number`, x, y, z, pause, heading) "
return grid_num; "VALUES (%i, %i, %i, %f, %f, %f, %i, %f)",
grid_num, zoneid, next_wp_num, xpos, ypos, zpos, pause, heading);
return 0; results = QueryDatabase(query);
} /*** END ZoneDatabase::AddWPForSpawn() ***/ 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) { uint32 ZoneDatabase::GetFreeGrid(uint16 zoneid) {
char *query = 0;
char errbuf[MYSQL_ERRMSG_SIZE]; std::string query = StringFormat("SELECT max(id) FROM grid WHERE zoneid = %i", zoneid);
MYSQL_RES *result; auto results = QueryDatabase(query);
MYSQL_ROW row; if (!results.Success()) {
if (RunQuery(query, MakeAnyLenString(&query,"SELECT max(id) from grid where zoneid = %i",zoneid),errbuf,&result)) { LogFile->write(EQEMuLog::Error, "Error in GetFreeGrid query '%s': %s", query.c_str(), results.ErrorMessage().c_str());
safe_delete_array(query); return 0;
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);
} }
else {
LogFile->write(EQEMuLog::Error, "Error in GetFreeGrid query '%s': %s", query, errbuf); if (results.RowCount() != 1)
safe_delete_array(query); return 0;
}
return 0; auto row = results.begin();
uint32 freeGridID = 1;
freeGridID = atoi(row[0]) + 1;
return freeGridID;
} }
int ZoneDatabase::GetHighestWaypoint(uint32 zoneid, uint32 gridid) { int ZoneDatabase::GetHighestWaypoint(uint32 zoneid, uint32 gridid) {
char *query = 0;
char errbuff[MYSQL_ERRMSG_SIZE]; std::string query = StringFormat("SELECT COALESCE(MAX(number), 0) FROM grid_entries "
MYSQL_RES *result; "WHERE zoneid = %i AND gridid = %i", zoneid, gridid);
MYSQL_ROW row; auto results = QueryDatabase(query);
int res = 0; if (!results.Success()) {
if (RunQuery(query, MakeAnyLenString(&query, LogFile->write(EQEMuLog::Error, "Error in GetHighestWaypoint query '%s': %s", query.c_str(), results.ErrorMessage().c_str());
"SELECT COALESCE(MAX(number), 0) FROM grid_entries WHERE zoneid = %i AND gridid = %i", return 0;
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);
} }
return(res); if (results.RowCount() != 1)
return 0;
auto row = results.begin();
return atoi(row[0]);
} }
void NPC::SaveGuardSpotCharm() void NPC::SaveGuardSpotCharm()

View File

@ -1691,45 +1691,41 @@ ZonePoint* Zone::GetClosestZonePointWithoutZone(float x, float y, float z, Clien
bool ZoneDatabase::LoadStaticZonePoints(LinkedList<ZonePoint*>* zone_point_list, const char* zonename, uint32 version) bool ZoneDatabase::LoadStaticZonePoints(LinkedList<ZonePoint*>* 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_point_list->Clear();
zone->numzonepoints = 0; zone->numzonepoints = 0;
MakeAnyLenString(&query, "SELECT x, y, z, target_x, target_y, " std::string query = StringFormat("SELECT x, y, z, target_x, target_y, "
"target_z, target_zone_id, heading, target_heading, number, " "target_z, target_zone_id, heading, target_heading, "
"target_instance, client_version_mask FROM zone_points " "number, target_instance, client_version_mask "
"WHERE zone='%s' AND (version=%i OR version=-1) order by number", zonename, version); "FROM zone_points WHERE zone='%s' AND (version=%i OR version=-1) "
if (RunQuery(query, strlen(query), errbuf, &result)) "ORDER BY number", zonename, version);
{ auto results = QueryDatabase(query);
safe_delete_array(query); if (!results.Success()) {
while((row = mysql_fetch_row(result))) std::cerr << "Error1 in LoadStaticZonePoints query '" << query << "' " << results.ErrorMessage() << std::endl;
{
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);
return false; 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; return true;
} }