Merge from master

This commit is contained in:
akkadius 2014-09-17 20:16:37 -05:00
commit 34c3b8628e
31 changed files with 2373 additions and 2861 deletions

View File

@ -1,5 +1,33 @@
EQEMu Changelog (Started on Sept 24, 2003 15:50)
-------------------------------------------------------
== 09/16/2014 ==
demonstar55: Implement spell formula 137 (BER AA Desperation)
Uleat (NateDog): Fix for LoadBuffs() crash when a spell with a non-persistent Illusion effect was loaded.
demonstar55: Fix some effect calcs + implement more (derived from the client)
== 09/15/2014 ==
Kayen: Nimbus effects will now be reapplied after zoning and will be removed when associated buff fades.
== 09/13/2014 ==
demonstar55: Fix rogues not having Thieves' Cant
== 09/09/2014 ==
demonstar55: Incrase Mob kick/bash timer by 3
see: http://www.eqemulator.org/forums/showthread.php?t=38734
demonstar55: Fix slow effect on NPC special attack reuse timers
see: http://www.eqemulator.org/forums/showthread.php?t=38734
demonstar55: Slow fixes to bots!
demonstar55: Revamped how NPC attack rate is set
SQL: 2014_09_09_attack_delay.sql
demonstar55: Added attackdelay to #npcedit
== 09/08/2014 ==
demonstar55: Fix slow calc
see: http://www.eqemulator.org/forums/showthread.php?t=38734
== 09/07/2014 ==
Akkadius: Fixed ROF Augment item dupe with not checking for available slots properly and adding items to the virtual instance
== 09/06/2014 ==
Uleat: Tweaked 'Smart' trading code to return main slots before sub slots in stackable and free space search processes. (If enough people ask for it, I'll add an optional rule to allow 'bag packing' - the original implementation behavior)

View File

@ -272,7 +272,7 @@ bool Database::DeleteAccount(const char* name) {
}
bool Database::SetLocalPassword(uint32 accid, const char* password) {
std::string query = StringFormat("UPDATE `account` SET `password` = MD5('%s') where id=%i;", password, accid);
std::string query = StringFormat("UPDATE account SET password=MD5('%s') where id=%i;", EscapeString(password).c_str(), accid);
auto results = QueryDatabase(query);

View File

@ -1582,13 +1582,12 @@ int8 ItemInst::AvailableAugmentSlot(int32 augtype) const
bool ItemInst::IsAugmentSlotAvailable(int32 augtype, uint8 slot) const {
if (m_item->ItemClass != ItemClassCommon || !m_item)
return -1;
return false;
if ((!GetItem(slot) && m_item->AugSlotVisible[slot]) && augtype == -1 || (m_item->AugSlotType[slot] && ((1 << (m_item->AugSlotType[slot] - 1)) & augtype))) {
return true;
}
return false;
return false;
}
// Retrieve item inside container

View File

@ -212,10 +212,6 @@ RULE_REAL ( Map, FixPathingZMaxDeltaMoving, 20 ) //at runtime while pathing: max
RULE_REAL ( Map, FixPathingZMaxDeltaWaypoint, 20 ) //at runtime at each waypoint: max change in Z to allow the BestZ code to apply.
RULE_REAL ( Map, FixPathingZMaxDeltaSendTo, 20 ) //at runtime in SendTo: max change in Z to allow the BestZ code to apply.
RULE_REAL ( Map, FixPathingZMaxDeltaLoading, 45 ) //while loading each waypoint: max change in Z to allow the BestZ code to apply.
RULE_BOOL ( Map, UseClosestZ, false) // Move mobs to the nearest Z above or below, rather than just the nearest below.
// Only set UseClosestZ true if all your .map files generated from EQGs were created
// with azone2.
//
RULE_INT ( Map, FindBestZHeightAdjust, 1) // Adds this to the current Z before seeking the best Z position
RULE_CATEGORY_END()

View File

@ -23,7 +23,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errmsg.h>
#include <errmsg.h>
#include <mysqld_error.h>
#include <limits.h>
#include <ctype.h>
@ -96,191 +96,253 @@ Close the connection to the database
Database::~Database()
{
}
void Database::AddSpeech(const char* from, const char* to, const char* message, uint16 minstatus, uint32 guilddbid, uint8 type) {
char errbuf[MYSQL_ERRMSG_SIZE];
char* query = 0;
char *S1 = new char[strlen(from) * 2 + 1];
char *S2 = new char[strlen(to) * 2 + 1];
char *S3 = new char[strlen(message) * 2 + 1];
DoEscapeString(S1, from, strlen(from));
DoEscapeString(S2, to, strlen(to));
DoEscapeString(S3, message, strlen(message));
char *escapedFrom = new char[strlen(from) * 2 + 1];
char *escapedTo = new char[strlen(to) * 2 + 1];
char *escapedMessage = new char[strlen(message) * 2 + 1];
DoEscapeString(escapedFrom, from, strlen(from));
DoEscapeString(escapedTo, to, strlen(to));
DoEscapeString(escapedMessage, message, strlen(message));
if(!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO `qs_player_speech` SET `from`='%s', `to`='%s', `message`='%s', `minstatus`='%i', `guilddbid`='%i', `type`='%i'", S1, S2, S3, minstatus, guilddbid, type), errbuf, 0, 0)) {
_log(QUERYSERV__ERROR, "Failed Speech Entry Insert: %s", errbuf);
_log(QUERYSERV__ERROR, "%s", query);
std::string query = StringFormat("INSERT INTO `qs_player_speech` "
"SET `from` = '%s', `to` = '%s', `message`='%s', "
"`minstatus`='%i', `guilddbid`='%i', `type`='%i'",
escapedFrom, escapedTo, escapedMessage, minstatus, guilddbid, type);
safe_delete_array(escapedFrom);
safe_delete_array(escapedTo);
safe_delete_array(escapedMessage);
auto results = QueryDatabase(query);
if(!results.Success()) {
_log(QUERYSERV__ERROR, "Failed Speech Entry Insert: %s", results.ErrorMessage().c_str());
_log(QUERYSERV__ERROR, "%s", query.c_str());
}
safe_delete_array(query);
safe_delete_array(S1);
safe_delete_array(S2);
safe_delete_array(S3);
}
void Database::LogPlayerTrade(QSPlayerLogTrade_Struct* QS, uint32 DetailCount) {
void Database::LogPlayerTrade(QSPlayerLogTrade_Struct* QS, uint32 detailCount) {
char errbuf[MYSQL_ERRMSG_SIZE];
char* query = 0;
uint32 lastid = 0;
if(!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO `qs_player_trade_record` SET `time`=NOW(), "
"`char1_id`='%i', `char1_pp`='%i', `char1_gp`='%i', `char1_sp`='%i', `char1_cp`='%i', `char1_items`='%i', "
"`char2_id`='%i', `char2_pp`='%i', `char2_gp`='%i', `char2_sp`='%i', `char2_cp`='%i', `char2_items`='%i'",
QS->char1_id, QS->char1_money.platinum, QS->char1_money.gold, QS->char1_money.silver, QS->char1_money.copper, QS->char1_count,
QS->char2_id, QS->char2_money.platinum, QS->char2_money.gold, QS->char2_money.silver, QS->char2_money.copper, QS->char2_count),
errbuf, 0, 0, &lastid)) {
_log(QUERYSERV__ERROR, "Failed Trade Log Record Insert: %s", errbuf);
_log(QUERYSERV__ERROR, "%s", query);
std::string query = StringFormat("INSERT INTO `qs_player_trade_record` SET `time` = NOW(), "
"`char1_id` = '%i', `char1_pp` = '%i', `char1_gp` = '%i', "
"`char1_sp` = '%i', `char1_cp` = '%i', `char1_items` = '%i', "
"`char2_id` = '%i', `char2_pp` = '%i', `char2_gp` = '%i', "
"`char2_sp` = '%i', `char2_cp` = '%i', `char2_items` = '%i'",
QS->char1_id, QS->char1_money.platinum, QS->char1_money.gold,
QS->char1_money.silver, QS->char1_money.copper, QS->char1_count,
QS->char2_id, QS->char2_money.platinum, QS->char2_money.gold,
QS->char2_money.silver, QS->char2_money.copper, QS->char2_count);
auto results = QueryDatabase(query);
if(!results.Success()) {
_log(QUERYSERV__ERROR, "Failed Trade Log Record Insert: %s", results.ErrorMessage().c_str());
_log(QUERYSERV__ERROR, "%s", query.c_str());
}
if(DetailCount > 0) {
for(int i = 0; i < DetailCount; i++) {
if(!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO `qs_player_trade_record_entries` SET `event_id`='%i', "
"`from_id`='%i', `from_slot`='%i', `to_id`='%i', `to_slot`='%i', `item_id`='%i', "
"`charges`='%i', `aug_1`='%i', `aug_2`='%i', `aug_3`='%i', `aug_4`='%i', `aug_5`='%i'",
lastid, QS->items[i].from_id, QS->items[i].from_slot, QS->items[i].to_id, QS->items[i].to_slot, QS->items[i].item_id,
QS->items[i].charges, QS->items[i].aug_1, QS->items[i].aug_2, QS->items[i].aug_3, QS->items[i].aug_4, QS->items[i].aug_5,
errbuf, 0, 0))) {
_log(QUERYSERV__ERROR, "Failed Trade Log Record Entry Insert: %s", errbuf);
_log(QUERYSERV__ERROR, "%s", query);
}
if(detailCount == 0)
return;
int lastIndex = results.LastInsertedID();
for(int i = 0; i < detailCount; i++) {
query = StringFormat("INSERT INTO `qs_player_trade_record_entries` SET `event_id` = '%i', "
"`from_id` = '%i', `from_slot` = '%i', `to_id` = '%i', `to_slot` = '%i', "
"`item_id` = '%i', `charges` = '%i', `aug_1` = '%i', `aug_2` = '%i', "
"`aug_3` = '%i', `aug_4` = '%i', `aug_5` = '%i'",
lastIndex, QS->items[i].from_id, QS->items[i].from_slot,
QS->items[i].to_id, QS->items[i].to_slot, QS->items[i].item_id,
QS->items[i].charges, QS->items[i].aug_1, QS->items[i].aug_2,
QS->items[i].aug_3, QS->items[i].aug_4, QS->items[i].aug_5);
results = QueryDatabase(query);
if(!results.Success()) {
_log(QUERYSERV__ERROR, "Failed Trade Log Record Entry Insert: %s", results.ErrorMessage().c_str());
_log(QUERYSERV__ERROR, "%s", query.c_str());
}
}
}
void Database::LogPlayerHandin(QSPlayerLogHandin_Struct* QS, uint32 detailCount) {
std::string query = StringFormat("INSERT INTO `qs_player_handin_record` SET `time` = NOW(), "
"`quest_id` = '%i', `char_id` = '%i', `char_pp` = '%i', "
"`char_gp` = '%i', `char_sp` = '%i', `char_cp` = '%i', "
"`char_items` = '%i', `npc_id` = '%i', `npc_pp` = '%i', "
"`npc_gp` = '%i', `npc_sp` = '%i', `npc_cp` = '%i', "
"`npc_items`='%i'",
QS->quest_id, QS->char_id, QS->char_money.platinum,
QS->char_money.gold, QS->char_money.silver, QS->char_money.copper,
QS->char_count, QS->npc_id, QS->npc_money.platinum,
QS->npc_money.gold, QS->npc_money.silver, QS->npc_money.copper,
QS->npc_count);
auto results = QueryDatabase(query);
if(!results.Success()) {
_log(QUERYSERV__ERROR, "Failed Handin Log Record Insert: %s", results.ErrorMessage().c_str());
_log(QUERYSERV__ERROR, "%s", query.c_str());
}
if(detailCount == 0)
return;
int lastIndex = results.LastInsertedID();
for(int i = 0; i < detailCount; i++) {
query = StringFormat("INSERT INTO `qs_player_handin_record_entries` SET `event_id` = '%i', "
"`action_type` = '%s', `char_slot` = '%i', `item_id` = '%i', "
"`charges` = '%i', `aug_1` = '%i', `aug_2` = '%i', `aug_3` = '%i', "
"`aug_4` = '%i', `aug_5` = '%i'",
lastIndex, QS->items[i].action_type, QS->items[i].char_slot,
QS->items[i].item_id, QS->items[i].charges, QS->items[i].aug_1,
QS->items[i].aug_2, QS->items[i].aug_3, QS->items[i].aug_4,
QS->items[i].aug_5);
if(!results.Success()) {
_log(QUERYSERV__ERROR, "Failed Handin Log Record Entry Insert: %s", results.ErrorMessage().c_str());
_log(QUERYSERV__ERROR, "%s", query.c_str());
}
}
}
void Database::LogPlayerNPCKill(QSPlayerLogNPCKill_Struct* QS, uint32 members){
std::string query = StringFormat("INSERT INTO `qs_player_npc_kill_record` "
"SET `npc_id` = '%i', `type` = '%i', "
"`zone_id` = '%i', `time` = NOW()",
QS->s1.NPCID, QS->s1.Type, QS->s1.ZoneID);
auto results = QueryDatabase(query);
if(!results.Success()) {
_log(QUERYSERV__ERROR, "Failed NPC Kill Log Record Insert: %s", results.ErrorMessage().c_str());
_log(QUERYSERV__ERROR, "%s", query.c_str());
}
if(members == 0)
return;
int lastIndex = results.LastInsertedID();
for (int i = 0; i < members; i++) {
query = StringFormat("INSERT INTO `qs_player_npc_kill_record_entries` "
"SET `event_id` = '%i', `char_id` = '%i'",
lastIndex, QS->Chars[i].char_id);
auto results = QueryDatabase(query);
if(!results.Success()) {
_log(QUERYSERV__ERROR, "Failed NPC Kill Log Entry Insert: %s", results.ErrorMessage().c_str());
_log(QUERYSERV__ERROR, "%s", query.c_str());
}
}
}
void Database::LogPlayerHandin(QSPlayerLogHandin_Struct* QS, uint32 DetailCount) {
char errbuf[MYSQL_ERRMSG_SIZE];
char* query = 0;
uint32 lastid = 0;
if(!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO `qs_player_handin_record` SET `time`=NOW(), `quest_id`='%i', "
"`char_id`='%i', `char_pp`='%i', `char_gp`='%i', `char_sp`='%i', `char_cp`='%i', `char_items`='%i', "
"`npc_id`='%i', `npc_pp`='%i', `npc_gp`='%i', `npc_sp`='%i', `npc_cp`='%i', `npc_items`='%i'",
QS->quest_id, QS->char_id, QS->char_money.platinum, QS->char_money.gold, QS->char_money.silver, QS->char_money.copper, QS->char_count,
QS->npc_id, QS->npc_money.platinum, QS->npc_money.gold, QS->npc_money.silver, QS->npc_money.copper, QS->npc_count),
errbuf, 0, 0, &lastid)) {
_log(QUERYSERV__ERROR, "Failed Handin Log Record Insert: %s", errbuf);
_log(QUERYSERV__ERROR, "%s", query);
void Database::LogPlayerDelete(QSPlayerLogDelete_Struct* QS, uint32 items) {
std::string query = StringFormat("INSERT INTO `qs_player_delete_record` SET `time` = NOW(), "
"`char_id` = '%i', `stack_size` = '%i', `char_items` = '%i'",
QS->char_id, QS->stack_size, QS->char_count, QS->char_count);
auto results = QueryDatabase(query);
if(!results.Success()) {
_log(QUERYSERV__ERROR, "Failed Delete Log Record Insert: %s", results.ErrorMessage().c_str());
_log(QUERYSERV__ERROR, "%s", query.c_str());
}
if(DetailCount > 0) {
for(int i = 0; i < DetailCount; i++) {
if(!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO `qs_player_handin_record_entries` SET `event_id`='%i', "
"`action_type`='%s', `char_slot`='%i', `item_id`='%i', `charges`='%i', "
"`aug_1`='%i', `aug_2`='%i', `aug_3`='%i', `aug_4`='%i', `aug_5`='%i'",
lastid, QS->items[i].action_type, QS->items[i].char_slot, QS->items[i].item_id, QS->items[i].charges,
QS->items[i].aug_1, QS->items[i].aug_2, QS->items[i].aug_3, QS->items[i].aug_4, QS->items[i].aug_5,
errbuf, 0, 0))) {
_log(QUERYSERV__ERROR, "Failed Handin Log Record Entry Insert: %s", errbuf);
_log(QUERYSERV__ERROR, "%s", query);
}
}
}
if(items == 0)
return;
int lastIndex = results.LastInsertedID();
for(int i = 0; i < items; i++) {
query = StringFormat("INSERT INTO `qs_player_delete_record_entries` SET `event_id` = '%i', "
"`char_slot` = '%i', `item_id` = '%i', `charges` = '%i', `aug_1` = '%i', "
"`aug_2` = '%i', `aug_3` = '%i', `aug_4` = '%i', `aug_5` = '%i'",
lastIndex, QS->items[i].char_slot, QS->items[i].item_id, QS->items[i].charges,
QS->items[i].aug_1, QS->items[i].aug_2, QS->items[i].aug_3, QS->items[i].aug_4,
QS->items[i].aug_5);
results = QueryDatabase(query);
if(!results.Success()) {
_log(QUERYSERV__ERROR, "Failed Delete Log Record Entry Insert: %s", results.ErrorMessage().c_str());
_log(QUERYSERV__ERROR, "%s", query.c_str());
}
}
}
void Database::LogPlayerNPCKill(QSPlayerLogNPCKill_Struct* QS, uint32 Members){
char errbuf[MYSQL_ERRMSG_SIZE];
char* query = 0;
uint32 lastid = 0;
if(!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO `qs_player_npc_kill_record` SET `npc_id`='%i', `type`='%i', `zone_id`='%i', `time`=NOW()", QS->s1.NPCID, QS->s1.Type, QS->s1.ZoneID), errbuf, 0, 0, &lastid)) {
_log(QUERYSERV__ERROR, "Failed NPC Kill Log Record Insert: %s", errbuf);
_log(QUERYSERV__ERROR, "%s", query);
}
if(Members > 0){
for (int i = 0; i < Members; i++) {
if(!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO `qs_player_npc_kill_record_entries` SET `event_id`='%i', `char_id`='%i'", lastid, QS->Chars[i].char_id, errbuf, 0, 0))) {
_log(QUERYSERV__ERROR, "Failed NPC Kill Log Entry Insert: %s", errbuf);
_log(QUERYSERV__ERROR, "%s", query);
}
}
}
}
void Database::LogPlayerDelete(QSPlayerLogDelete_Struct* QS, uint32 Items) {
char errbuf[MYSQL_ERRMSG_SIZE];
char* query = 0;
uint32 lastid = 0;
if(!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO `qs_player_delete_record` SET `time`=NOW(), "
"`char_id`='%i', `stack_size`='%i', `char_items`='%i'",
QS->char_id, QS->stack_size, QS->char_count, QS->char_count),
errbuf, 0, 0, &lastid)) {
_log(QUERYSERV__ERROR, "Failed Delete Log Record Insert: %s", errbuf);
_log(QUERYSERV__ERROR, "%s", query);
}
if(Items > 0) {
for(int i = 0; i < Items; i++) {
if(!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO `qs_player_delete_record_entries` SET `event_id`='%i', "
"`char_slot`='%i', `item_id`='%i', `charges`='%i', `aug_1`='%i', "
"`aug_2`='%i', `aug_3`='%i', `aug_4`='%i', `aug_5`='%i'",
lastid, QS->items[i].char_slot, QS->items[i].item_id, QS->items[i].charges, QS->items[i].aug_1,
QS->items[i].aug_2, QS->items[i].aug_3, QS->items[i].aug_4, QS->items[i].aug_5,
errbuf, 0, 0))) {
_log(QUERYSERV__ERROR, "Failed Delete Log Record Entry Insert: %s", errbuf);
_log(QUERYSERV__ERROR, "%s", query);
}
}
}
}
void Database::LogPlayerMove(QSPlayerLogMove_Struct* QS, uint32 Items) {
void Database::LogPlayerMove(QSPlayerLogMove_Struct* QS, uint32 items) {
/* These are item moves */
char errbuf[MYSQL_ERRMSG_SIZE];
char* query = 0;
uint32 lastid = 0;
if(!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO `qs_player_move_record` SET `time`=NOW(), "
"`char_id`='%i', `from_slot`='%i', `to_slot`='%i', `stack_size`='%i', `char_items`='%i', `postaction`='%i'",
QS->char_id, QS->from_slot, QS->to_slot, QS->stack_size, QS->char_count, QS->postaction),
errbuf, 0, 0, &lastid)) {
_log(QUERYSERV__ERROR, "Failed Move Log Record Insert: %s", errbuf);
_log(QUERYSERV__ERROR, "%s", query);
}
if(Items > 0) {
for(int i = 0; i < Items; i++) {
if(!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO `qs_player_move_record_entries` SET `event_id`='%i', "
"`from_slot`='%i', `to_slot`='%i', `item_id`='%i', `charges`='%i', "
"`aug_1`='%i', `aug_2`='%i', `aug_3`='%i', `aug_4`='%i', `aug_5`='%i'", lastid,
QS->items[i].from_slot, QS->items[i].to_slot, QS->items[i].item_id, QS->items[i].charges,
QS->items[i].aug_1, QS->items[i].aug_2, QS->items[i].aug_3, QS->items[i].aug_4, QS->items[i].aug_5,
errbuf, 0, 0))) {
_log(QUERYSERV__ERROR, "Failed Move Log Record Entry Insert: %s", errbuf);
_log(QUERYSERV__ERROR, "%s", query);
}
}
std::string query = StringFormat("INSERT INTO `qs_player_move_record` SET `time` = NOW(), "
"`char_id` = '%i', `from_slot` = '%i', `to_slot` = '%i', "
"`stack_size` = '%i', `char_items` = '%i', `postaction` = '%i'",
QS->char_id, QS->from_slot, QS->to_slot, QS->stack_size,
QS->char_count, QS->postaction);
auto results = QueryDatabase(query);
if(!results.Success()) {
_log(QUERYSERV__ERROR, "Failed Move Log Record Insert: %s", results.ErrorMessage().c_str());
_log(QUERYSERV__ERROR, "%s", query.c_str());
}
if(items == 0)
return;
int lastIndex = results.LastInsertedID();
for(int i = 0; i < items; i++) {
query = StringFormat("INSERT INTO `qs_player_move_record_entries` SET `event_id` = '%i', "
"`from_slot` = '%i', `to_slot` = '%i', `item_id` = '%i', `charges` = '%i', "
"`aug_1` = '%i', `aug_2` = '%i', `aug_3` = '%i', `aug_4` = '%i', `aug_5` = '%i'",
lastIndex, QS->items[i].from_slot, QS->items[i].to_slot, QS->items[i].item_id,
QS->items[i].charges, QS->items[i].aug_1, QS->items[i].aug_2,
QS->items[i].aug_3, QS->items[i].aug_4, QS->items[i].aug_5);
results = QueryDatabase(query);
if(!results.Success()) {
_log(QUERYSERV__ERROR, "Failed Move Log Record Entry Insert: %s", results.ErrorMessage().c_str());
_log(QUERYSERV__ERROR, "%s", query.c_str());
}
}
}
void Database::LogMerchantTransaction(QSMerchantLogTransaction_Struct* QS, uint32 Items) {
void Database::LogMerchantTransaction(QSMerchantLogTransaction_Struct* QS, uint32 items) {
/* Merchant transactions are from the perspective of the merchant, not the player -U */
char errbuf[MYSQL_ERRMSG_SIZE];
char* query = 0;
uint32 lastid = 0;
if(!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO `qs_merchant_transaction_record` SET `time`=NOW(), "
"`zone_id`='%i', `merchant_id`='%i', `merchant_pp`='%i', `merchant_gp`='%i', `merchant_sp`='%i', `merchant_cp`='%i', `merchant_items`='%i', "
"`char_id`='%i', `char_pp`='%i', `char_gp`='%i', `char_sp`='%i', `char_cp`='%i', `char_items`='%i'",
QS->zone_id, QS->merchant_id, QS->merchant_money.platinum, QS->merchant_money.gold, QS->merchant_money.silver, QS->merchant_money.copper, QS->merchant_count,
QS->char_id, QS->char_money.platinum, QS->char_money.gold, QS->char_money.silver, QS->char_money.copper, QS->char_count),
errbuf, 0, 0, &lastid)) {
_log(QUERYSERV__ERROR, "Failed Transaction Log Record Insert: %s", errbuf);
_log(QUERYSERV__ERROR, "%s", query);
std::string query = StringFormat("INSERT INTO `qs_merchant_transaction_record` SET `time` = NOW(), "
"`zone_id` = '%i', `merchant_id` = '%i', `merchant_pp` = '%i', "
"`merchant_gp` = '%i', `merchant_sp` = '%i', `merchant_cp` = '%i', "
"`merchant_items` = '%i', `char_id` = '%i', `char_pp` = '%i', "
"`char_gp` = '%i', `char_sp` = '%i', `char_cp` = '%i', "
"`char_items` = '%i'",
QS->zone_id, QS->merchant_id, QS->merchant_money.platinum,
QS->merchant_money.gold, QS->merchant_money.silver,
QS->merchant_money.copper, QS->merchant_count, QS->char_id,
QS->char_money.platinum, QS->char_money.gold, QS->char_money.silver,
QS->char_money.copper, QS->char_count);
auto results = QueryDatabase(query);
if(!results.Success()) {
_log(QUERYSERV__ERROR, "Failed Transaction Log Record Insert: %s", results.ErrorMessage().c_str());
_log(QUERYSERV__ERROR, "%s", query.c_str());
}
if(Items > 0) {
for(int i = 0; i < Items; i++) {
if(!RunQuery(query, MakeAnyLenString(&query, "INSERT INTO `qs_merchant_transaction_record_entries` SET `event_id`='%i', "
"`char_slot`='%i', `item_id`='%i', `charges`='%i', `aug_1`='%i', "
"`aug_2`='%i', `aug_3`='%i', `aug_4`='%i', `aug_5`='%i'",
lastid, QS->items[i].char_slot, QS->items[i].item_id, QS->items[i].charges, QS->items[i].aug_1,
QS->items[i].aug_2, QS->items[i].aug_3, QS->items[i].aug_4, QS->items[i].aug_5,
errbuf, 0, 0))) {
_log(QUERYSERV__ERROR, "Failed Transaction Log Record Entry Insert: %s", errbuf);
_log(QUERYSERV__ERROR, "%s", query);
}
}
}
safe_delete_array(query);
if(items == 0)
return;
int lastIndex = results.LastInsertedID();
for(int i = 0; i < items; i++) {
query = StringFormat("INSERT INTO `qs_merchant_transaction_record_entries` SET `event_id` = '%i', "
"`char_slot` = '%i', `item_id` = '%i', `charges` = '%i', `aug_1` = '%i', "
"`aug_2` = '%i', `aug_3` = '%i', `aug_4` = '%i', `aug_5` = '%i'",
lastIndex, QS->items[i].char_slot, QS->items[i].item_id, QS->items[i].charges,
QS->items[i].aug_1, QS->items[i].aug_2, QS->items[i].aug_3, QS->items[i].aug_4,
QS->items[i].aug_5);
results = QueryDatabase(query);
if(!results.Success()) {
_log(QUERYSERV__ERROR, "Failed Transaction Log Record Entry Insert: %s", results.ErrorMessage().c_str());
_log(QUERYSERV__ERROR, "%s", query.c_str());
}
}
}
void Database::GeneralQueryReceive(ServerPacket *pack) {
@ -293,11 +355,11 @@ void Database::GeneralQueryReceive(ServerPacket *pack) {
char errbuf[MYSQL_ERRMSG_SIZE];
char* query = 0;
uint32 lastid = 0;
if (!RunQuery(query, MakeAnyLenString(&query, Query), errbuf, 0, 0, &lastid)) {
if (!RunQuery(query, MakeAnyLenString(&query, Query), errbuf, 0, 0, &lastid)) {
_log(QUERYSERV__ERROR, "Failed Delete Log Record Insert: %s", errbuf);
_log(QUERYSERV__ERROR, "%s", query);
_log(QUERYSERV__ERROR, "%s", query);
}
safe_delete_array(query);
safe_delete(pack);
safe_delete(Query);
safe_delete(pack);
safe_delete(Query);
}

View File

@ -0,0 +1,3 @@
ALTER TABLE `npc_types` ADD `attack_delay` TINYINT(3) UNSIGNED DEFAULT '30' NOT NULL AFTER `attack_speed`;
UPDATE `npc_types` SET `attack_delay` = 36 + 36 * (`attack_speed` / 100);
UPDATE `npc_types` SET `attack_delay` = 30 WHERE `attack_speed` = 0;

View File

@ -1402,6 +1402,7 @@ bool Client::OPCharCreate(char *name, CharCreate_Struct *cc) {
SetRacialLanguages( &pp );
SetRaceStartingSkills( &pp );
SetClassStartingSkills( &pp );
SetClassLanguages(&pp);
pp.skills[SkillSenseHeading] = 200;
// Some one fucking fix this to use a field name. -Doodman
//pp.unknown3596[28] = 15; // @bp: This is to enable disc usage
@ -1981,3 +1982,15 @@ void Client::SetRacialLanguages( PlayerProfile_Struct *pp )
}
}
void Client::SetClassLanguages(PlayerProfile_Struct *pp)
{
// we only need to handle one class, but custom server might want to do more
switch(pp->class_) {
case ROGUE:
pp->languages[LANG_THIEVES_CANT] = 100;
break;
default:
break;
}
}

View File

@ -90,6 +90,7 @@ private:
void SetClassStartingSkills( PlayerProfile_Struct *pp );
void SetRaceStartingSkills( PlayerProfile_Struct *pp );
void SetRacialLanguages( PlayerProfile_Struct *pp );
void SetClassLanguages(PlayerProfile_Struct *pp);
ClientListEntry* cle;
Timer CLE_keepalive_timer;

View File

@ -3516,7 +3516,7 @@ void Mob::CommonDamage(Mob* attacker, int32 &damage, const uint16 spell_id, cons
if(spell_id == SPELL_UNKNOWN) {
damage = ReduceDamage(damage);
mlog(COMBAT__HITS, "Melee Damage reduced to %d", damage);
ReduceAllDamage(damage);
damage = ReduceAllDamage(damage);
TryTriggerThreshHold(damage, SE_TriggerMeleeThreshold, attacker);
} else {
int32 origdmg = damage;
@ -3529,7 +3529,7 @@ void Mob::CommonDamage(Mob* attacker, int32 &damage, const uint16 spell_id, cons
//Kayen: Probably need to add a filter for this - Not sure if this msg is correct but there should be a message for spell negate/runes.
Message(263, "%s tries to cast on YOU, but YOUR magical skin absorbs the spell.",attacker->GetCleanName());
}
ReduceAllDamage(damage);
damage = ReduceAllDamage(damage);
TryTriggerThreshHold(damage, SE_TriggerSpellThreshold, attacker);
}
@ -4803,3 +4803,145 @@ void Mob::CommonBreakInvisible()
hidden = false;
improved_hidden = false;
}
void Mob::SetAttackTimer()
{
attack_timer.SetAtTrigger(4000, true);
}
void Client::SetAttackTimer()
{
float PermaHaste = GetPermaHaste();
//default value for attack timer in case they have
//an invalid weapon equipped:
attack_timer.SetAtTrigger(4000, true);
Timer *TimerToUse = nullptr;
const Item_Struct *PrimaryWeapon = nullptr;
for (int i = MainRange; i <= MainSecondary; i++) {
//pick a timer
if (i == MainPrimary)
TimerToUse = &attack_timer;
else if (i == MainRange)
TimerToUse = &ranged_timer;
else if (i == MainSecondary)
TimerToUse = &attack_dw_timer;
else //invalid slot (hands will always hit this)
continue;
const Item_Struct *ItemToUse = nullptr;
//find our item
ItemInst *ci = GetInv().GetItem(i);
if (ci)
ItemToUse = ci->GetItem();
//special offhand stuff
if (i == MainSecondary) {
//if we have a 2H weapon in our main hand, no dual
if (PrimaryWeapon != nullptr) {
if (PrimaryWeapon->ItemClass == ItemClassCommon
&& (PrimaryWeapon->ItemType == ItemType2HSlash
|| PrimaryWeapon->ItemType == ItemType2HBlunt
|| PrimaryWeapon->ItemType == ItemType2HPiercing)) {
attack_dw_timer.Disable();
continue;
}
}
//if we cant dual wield, skip it
if (!CanThisClassDualWield()) {
attack_dw_timer.Disable();
continue;
}
}
//see if we have a valid weapon
if (ItemToUse != nullptr) {
//check type and damage/delay
if (ItemToUse->ItemClass != ItemClassCommon
|| ItemToUse->Damage == 0
|| ItemToUse->Delay == 0) {
//no weapon
ItemToUse = nullptr;
}
// Check to see if skill is valid
else if ((ItemToUse->ItemType > ItemTypeLargeThrowing) &&
(ItemToUse->ItemType != ItemTypeMartial) &&
(ItemToUse->ItemType != ItemType2HPiercing)) {
//no weapon
ItemToUse = nullptr;
}
}
int16 DelayMod = std::max(itembonuses.HundredHands + spellbonuses.HundredHands, -99);
int speed = 0;
//if we have no weapon..
if (ItemToUse == nullptr) {
//above checks ensure ranged weapons do not fall into here
// Work out if we're a monk
if ((GetClass() == MONK) || (GetClass() == BEASTLORD))
speed = static_cast<int>((GetMonkHandToHandDelay() * (100 + DelayMod) / 100) * PermaHaste);
else
speed = static_cast<int>((36 * (100 + DelayMod) / 100) * PermaHaste);
} else {
//we have a weapon, use its delay
// Convert weapon delay to timer resolution (milliseconds)
//delay * 100
speed = static_cast<int>((ItemToUse->Delay * (100 + DelayMod) / 100) * PermaHaste);
if (ItemToUse->ItemType == ItemTypeBow || ItemToUse->ItemType == ItemTypeLargeThrowing) {
float quiver_haste = GetQuiverHaste();
if (quiver_haste > 0)
speed *= quiver_haste;
}
}
TimerToUse->SetAtTrigger(std::max(RuleI(Combat, MinHastedDelay), speed), true);
if (i == MainPrimary)
PrimaryWeapon = ItemToUse;
}
}
void NPC::SetAttackTimer()
{
float PermaHaste = GetPermaHaste();
//default value for attack timer in case they have
//an invalid weapon equipped:
attack_timer.SetAtTrigger(4000, true);
Timer *TimerToUse = nullptr;
for (int i = MainRange; i <= MainSecondary; i++) {
//pick a timer
if (i == MainPrimary)
TimerToUse = &attack_timer;
else if (i == MainRange)
TimerToUse = &ranged_timer;
else if (i == MainSecondary)
TimerToUse = &attack_dw_timer;
else //invalid slot (hands will always hit this)
continue;
//special offhand stuff
if (i == MainSecondary) {
//NPCs get it for free at 13
if(GetLevel() < 13) {
attack_dw_timer.Disable();
continue;
}
}
int16 DelayMod = std::max(itembonuses.HundredHands + spellbonuses.HundredHands, -99);
// Technically NPCs should do some logic for weapons, but the effect is minimal
// What they do is take the lower of their set delay and the weapon's
// ex. Mob's delay set to 20, weapon set to 19, delay 19
// Mob's delay set to 20, weapon set to 21, delay 20
int speed = static_cast<int>((attack_delay * (100 + DelayMod) / 100) * PermaHaste);
TimerToUse->SetAtTrigger(std::max(RuleI(Combat, MinHastedDelay), speed), true);
}
}

View File

@ -8350,12 +8350,10 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) {
return;
float HasteModifier = 0;
if(GetHaste() >= 0){
if (GetHaste())
HasteModifier = 10000 / (100 + GetHaste());
}
else {
HasteModifier = (100 - GetHaste());
}
else
HasteModifier = 100;
int32 dmg = 0;
uint16 skill_to_use = -1;
@ -8981,10 +8979,8 @@ int32 Bot::CalcMaxMana() {
void Bot::SetAttackTimer() {
float PermaHaste;
if(GetHaste() > 0)
if (GetHaste())
PermaHaste = 1 / (1 + (float)GetHaste()/100);
else if(GetHaste() < 0)
PermaHaste = 1 * (1 - (float)GetHaste()/100);
else
PermaHaste = 1.0f;

View File

@ -3949,54 +3949,38 @@ void Client::SendWindow(uint32 PopupID, uint32 NegativeID, uint32 Buttons, const
void Client::KeyRingLoad()
{
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
MYSQL_RES *result;
MYSQL_ROW row;
query = new char[256];
sprintf(query, "SELECT item_id FROM keyring WHERE char_id='%i' ORDER BY item_id",character_id);
if (database.RunQuery(query, strlen(query), errbuf, &result))
{
safe_delete_array(query);
while(0 != (row = mysql_fetch_row(result))){
keyring.push_back(atoi(row[0]));
}
mysql_free_result(result);
}else {
std::cerr << "Error in Client::KeyRingLoad query '" << query << "' " << errbuf << std::endl;
safe_delete_array(query);
std::string query = StringFormat("SELECT item_id FROM keyring "
"WHERE char_id = '%i' ORDER BY item_id", character_id);
auto results = database.QueryDatabase(query);
if (!results.Success()) {
std::cerr << "Error in Client::KeyRingLoad query '" << query << "' " << results.ErrorMessage() << std::endl;
return;
}
}
for (auto row = results.begin(); row != results.end(); ++row)
keyring.push_back(atoi(row[0]));
}
void Client::KeyRingAdd(uint32 item_id)
{
if(0==item_id)return;
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
uint32 affected_rows = 0;
query = new char[256];
bool bFound = KeyRingCheck(item_id);
if(!bFound){
sprintf(query, "INSERT INTO keyring(char_id,item_id) VALUES(%i,%i)",character_id,item_id);
if(database.RunQuery(query, strlen(query), errbuf, 0, &affected_rows)) {
Message(4,"Added to keyring.");
if(0==item_id)
return;
/* QS: PlayerLogKeyringAddition */
if (RuleB(QueryServ, PlayerLogKeyringAddition)){
std::string event_desc = StringFormat("itemid:%i in zoneid:%i instid:%i", item_id, this->GetZoneID(), this->GetInstanceID());
QServ->PlayerLogEvent(Player_Log_Keyring_Addition, this->CharacterID(), event_desc);
}
safe_delete_array(query);
}
else {
std::cerr << "Error in Doors::HandleClick query '" << query << "' " << errbuf << std::endl;
safe_delete_array(query);
return;
}
keyring.push_back(item_id);
}
bool found = KeyRingCheck(item_id);
if (found)
return;
std::string query = StringFormat("INSERT INTO keyring(char_id, item_id) VALUES(%i, %i)", character_id, item_id);
auto results = database.QueryDatabase(query);
if (!results.Success()) {
std::cerr << "Error in Doors::HandleClick query '" << query << "' " << results.ErrorMessage() << std::endl;
return;
}
Message(4,"Added to keyring.");
keyring.push_back(item_id);
}
bool Client::KeyRingCheck(uint32 item_id)
@ -8282,3 +8266,19 @@ void Client::ShowNumHits()
return;
}
float Client::GetQuiverHaste()
{
float quiver_haste = 0;
for (int r = EmuConstants::GENERAL_BEGIN; r <= EmuConstants::GENERAL_END; r++) {
const ItemInst *pi = GetInv().GetItem(r);
if (!pi)
continue;
if (pi->IsType(ItemClassContainer) && pi->GetItem()->BagType == BagTypeQuiver) {
float temp_wr = (pi->GetItem()->BagWR / RuleI(Combat, QuiverWRHasteDiv));
quiver_haste = std::max(temp_wr, quiver_haste);
}
}
if (quiver_haste > 0)
quiver_haste = 1.0f / (1.0f + static_cast<float>(quiver_haste) / 100.0f);
return quiver_haste;
}

View File

@ -218,6 +218,8 @@ public:
virtual Group* GetGroup() { return entity_list.GetGroupByClient(this); }
virtual inline bool IsBerserk() { return berserk; }
virtual int32 GetMeleeMitDmg(Mob *attacker, int32 damage, int32 minhit, float mit_rating, float atk_rating);
virtual void SetAttackTimer();
float GetQuiverHaste();
void AI_Init();
void AI_Start(uint32 iMoveDelay = 0);
@ -1193,9 +1195,9 @@ public:
int32 mod_client_xp(int32 in_exp, NPC *npc);
uint32 mod_client_xp_for_level(uint32 xp, uint16 check_level);
int mod_client_haste_cap(int cap);
int mod_consume(Item_Struct *item, ItemUseTypes type, int change);
int mod_food_value(const Item_Struct *item, int change);
int mod_drink_value(const Item_Struct *item, int change);
int mod_consume(Item_Struct *item, ItemUseTypes type, int change);
int mod_food_value(const Item_Struct *item, int change);
int mod_drink_value(const Item_Struct *item, int change);
void SetEngagedRaidTarget(bool value) { EngagedRaidTarget = value; }
bool GetEngagedRaidTarget() const { return EngagedRaidTarget; }

View File

@ -6781,7 +6781,7 @@ void Client::Handle_OP_AugmentItem(const EQApplicationPacket *app)
tobe_auged = user_inv.GetItem(slot_id);
auged_with = user_inv.GetItem(MainCursor);
if(tobe_auged && auged_with)
if (tobe_auged && auged_with)
{
if (((tobe_auged->IsAugmentSlotAvailable(auged_with->GetAugmentType(), in_augment->augment_index)) != -1) &&
(tobe_auged->AvailableWearSlot(auged_with->GetItem()->Slots)))
@ -9432,11 +9432,17 @@ void Client::CompleteConnect() {
//reapply some buffs
uint32 buff_count = GetMaxTotalSlots();
for (uint32 j1 = 0; j1 < buff_count; j1++) {
if (buffs[j1].spellid >(uint32)SPDAT_RECORDS)
if (!IsValidSpell(buffs[j1].spellid))
continue;
const SPDat_Spell_Struct &spell = spells[buffs[j1].spellid];
int NimbusEffect = GetNimbusEffect(buffs[j1].spellid);
if(NimbusEffect) {
if(!IsNimbusEffectActive(NimbusEffect))
SendSpellEffect(NimbusEffect, 500, 0, 1, 3000, true);
}
for (int x1 = 0; x1 < EFFECT_COUNT; x1++) {
switch (spell.effectid[x1]) {
case SE_IllusionCopy:

View File

@ -6555,6 +6555,7 @@ void command_npcedit(Client *c, const Seperator *sep)
c->Message(0, "#npcedit qglobal - Sets an NPC's quest global flag");
c->Message(0, "#npcedit limit - Sets an NPC's spawn limit counter");
c->Message(0, "#npcedit Attackspeed - Sets an NPC's attack speed modifier");
c->Message(0, "#npcedit Attackdelay - Sets an NPC's attack delay");
c->Message(0, "#npcedit findable - Sets an NPC's findable flag");
c->Message(0, "#npcedit wep1 - Sets an NPC's primary weapon model");
c->Message(0, "#npcedit wep2 - Sets an NPC's secondary weapon model");
@ -7056,6 +7057,15 @@ void command_npcedit(Client *c, const Seperator *sep)
c->LogSQL(query);
safe_delete_array(query);
}
else if ( strcasecmp( sep->arg[1], "Attackdelay" ) == 0 )
{
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
c->Message(15,"NPCID %u now has attack_delay set to %i",c->GetTarget()->CastToNPC()->GetNPCTypeID(),atoi(sep->arg[2]));
database.RunQuery(query, MakeAnyLenString(&query, "update npc_types set attack_delay=%i where id=%i",atoi(sep->argplus[2]),c->GetTarget()->CastToNPC()->GetNPCTypeID()), errbuf);
c->LogSQL(query);
safe_delete_array(query);
}
else if ( strcasecmp( sep->arg[1], "findable" ) == 0 )
{
char errbuf[MYSQL_ERRMSG_SIZE];

View File

@ -356,9 +356,11 @@ void Client::GoFish()
inst = m_inv.GetItem(MainCursor);
}
std::vector<EQEmu::Any> args;
args.push_back(inst);
parse->EventPlayer(EVENT_FISH_SUCCESS, this, "", inst->GetID(), &args);
if(inst) {
std::vector<EQEmu::Any> args;
args.push_back(inst);
parse->EventPlayer(EVENT_FISH_SUCCESS, this, "", inst->GetID(), &args);
}
}
}
else
@ -470,9 +472,11 @@ void Client::ForageItem(bool guarantee) {
inst = m_inv.GetItem(MainCursor);
}
std::vector<EQEmu::Any> args;
args.push_back(inst);
parse->EventPlayer(EVENT_FORAGE_SUCCESS, this, "", inst->GetID(), &args);
if(inst) {
std::vector<EQEmu::Any> args;
args.push_back(inst);
parse->EventPlayer(EVENT_FORAGE_SUCCESS, this, "", inst->GetID(), &args);
}
}
int ChanceSecondForage = aabonuses.ForageAdditionalItems + itembonuses.ForageAdditionalItems + spellbonuses.ForageAdditionalItems;

View File

@ -561,6 +561,18 @@ bool Group::DelMember(Mob* oldmember,bool ignoresender)
}
}
/* This may seem pointless but the case above does not cover the following situation:
* Group has Leader a, member b, member c
* b and c are out of zone
* a disconnects/quits
* b or c zone back in and disconnects/quits
* a is still "leader" from GetLeader()'s perspective and will crash the zone when we DelMember(b)
* Ultimately we should think up a better solution to this.
*/
if(oldmember == GetLeader()) {
SetLeader(nullptr);
}
ServerPacket* pack = new ServerPacket(ServerOP_GroupLeave, sizeof(ServerGroupLeave_Struct));
ServerGroupLeave_Struct* gl = (ServerGroupLeave_Struct*)pack->pBuffer;
gl->gid = GetID();

View File

@ -165,7 +165,7 @@ void Client::SendGuildSpawnAppearance() {
switch (rank) {
case 0: { rank = 5; break; } // GUILD_MEMBER 0
case 1: { rank = 3; break; } // GUILD_OFFICER 1
case 2: { rank = 1; break; } // GUILD_LEADER 2
case 2: { rank = 1; break; } // GUILD_LEADER 2
default: { break; } // GUILD_NONE
}
}
@ -417,57 +417,36 @@ void Client::GuildChangeRank(const char* name, uint32 guild_id, uint32 oldrank,
}*/
bool ZoneDatabase::CheckGuildDoor(uint8 doorid,uint16 guild_id,const char* zone) {
MYSQL_ROW row;
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
MYSQL_RES *result;
if (!RunQuery(query, MakeAnyLenString(&query,
"SELECT guild FROM doors where doorid=%i AND zone='%s'",
doorid-128, zone), errbuf, &result))
{
LogFile->write(EQEMuLog::Error, "Error in CheckGuildDoor query '%s': %s", query, errbuf);
safe_delete_array(query);
return false;
} else {
if (mysql_num_rows(result) == 1) {
row = mysql_fetch_row(result);
if (atoi(row[0]) == guild_id)
{
mysql_free_result(result);
return true;
}
else
{
mysql_free_result(result);
return false;
}
bool ZoneDatabase::CheckGuildDoor(uint8 doorid, uint16 guild_id, const char* zone) {
// code below will never be reached
mysql_free_result(result);
return false;
}
std::string query = StringFormat("SELECT guild FROM doors WHERE doorid = %i AND zone = '%s'",
doorid-128, zone);
auto results = QueryDatabase(query);
if (!results.Success()) {
LogFile->write(EQEMuLog::Error, "Error in CheckGuildDoor query '%s': %s", query.c_str(), results.ErrorMessage().c_str());
return false;
}
return false;
if (results.RowCount() != 1)
return false;
auto row = results.begin();
return atoi(row[0]) == guild_id;
}
bool ZoneDatabase::SetGuildDoor(uint8 doorid,uint16 guild_id, const char* zone) {
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
uint32 affected_rows = 0;
if (doorid > 127)
doorid = doorid - 128;
if (!RunQuery(query, MakeAnyLenString(&query,
"UPDATE doors SET guild = %i WHERE (doorid=%i) AND (zone='%s')",
guild_id, doorid, zone), errbuf, 0,&affected_rows))
{
LogFile->write(EQEMuLog::Error, "Error in SetGuildDoor query '%s': %s", query, errbuf);
safe_delete_array(query);
std::string query = StringFormat("UPDATE doors SET guild = %i WHERE (doorid=%i) AND (zone='%s')",
guild_id, doorid, zone);
auto results = QueryDatabase(query);
if (!results.Success()) {
LogFile->write(EQEMuLog::Error, "Error in SetGuildDoor query '%s': %s", query.c_str(), results.ErrorMessage().c_str());
return false;
}
safe_delete_array(query);
return(affected_rows > 0);
return (results.RowsAffected() > 0);
}

View File

@ -850,24 +850,16 @@ bool GuildBankManager::AddItem(uint32 GuildID, uint8 Area, uint32 ItemID, int32
return false;
}
const char *Query="INSERT INTO `guild_bank` (`guildid`, `area`, `slot`, `itemid`, `qty`, `donator`, `permissions`, `WhoFor`) "
"VALUES (%i, %i, %i, %i, %i, '%s', %i, '%s')";
char errbuf[MYSQL_ERRMSG_SIZE];
char* query = 0;
if(!database.RunQuery(query, MakeAnyLenString(&query, Query, GuildID, Area, Slot, ItemID, QtyOrCharges, Donator, Permissions, WhoFor), errbuf))
{
_log(GUILDS__BANK_ERROR, "Insert Error: %s : %s", query, errbuf);
safe_delete_array(query);
std::string query = StringFormat("INSERT INTO `guild_bank` "
"(`guildid`, `area`, `slot`, `itemid`, `qty`, `donator`, `permissions`, `WhoFor`) "
"VALUES (%i, %i, %i, %i, %i, '%s', %i, '%s')",
GuildID, Area, Slot, ItemID, QtyOrCharges, Donator, Permissions, WhoFor);
auto results = database.QueryDatabase(query);
if(!results.Success()) {
_log(GUILDS__BANK_ERROR, "Insert Error: %s : %s", query.c_str(), results.ErrorMessage().c_str());
return false;
}
safe_delete_array(query);
const Item_Struct *Item = database.GetItem(ItemID);
GuildBankItemUpdate_Struct gbius;

View File

@ -174,8 +174,9 @@ Mob::Mob(const char* in_name,
drakkin_heritage = in_drakkin_heritage;
drakkin_tattoo = in_drakkin_tattoo;
drakkin_details = in_drakkin_details;
attack_speed= 0;
slow_mitigation= 0;
attack_speed = 0;
attack_delay = 0;
slow_mitigation = 0;
findable = false;
trackable = true;
has_shieldequiped = false;
@ -1940,159 +1941,6 @@ void Mob::Kill() {
Death(this, 0, SPELL_UNKNOWN, SkillHandtoHand);
}
void Mob::SetAttackTimer() {
float PermaHaste;
if(GetHaste() > 0)
PermaHaste = 1 / (1 + (float)GetHaste()/100);
else if(GetHaste() < 0)
PermaHaste = 1 * (1 - (float)GetHaste()/100);
else
PermaHaste = 1.0f;
//default value for attack timer in case they have
//an invalid weapon equipped:
attack_timer.SetAtTrigger(4000, true);
Timer* TimerToUse = nullptr;
const Item_Struct* PrimaryWeapon = nullptr;
for (int i=MainRange; i<=MainSecondary; i++) {
//pick a timer
if (i == MainPrimary)
TimerToUse = &attack_timer;
else if (i == MainRange)
TimerToUse = &ranged_timer;
else if(i == MainSecondary)
TimerToUse = &attack_dw_timer;
else //invalid slot (hands will always hit this)
continue;
const Item_Struct* ItemToUse = nullptr;
//find our item
if (IsClient()) {
ItemInst* ci = CastToClient()->GetInv().GetItem(i);
if (ci)
ItemToUse = ci->GetItem();
} else if(IsNPC())
{
//The code before here was fundementally flawed because equipment[]
//isn't the same as PC inventory and also:
//NPCs don't use weapon speed to dictate how fast they hit anyway.
ItemToUse = nullptr;
}
//special offhand stuff
if(i == MainSecondary) {
//if we have a 2H weapon in our main hand, no dual
if(PrimaryWeapon != nullptr) {
if( PrimaryWeapon->ItemClass == ItemClassCommon
&& (PrimaryWeapon->ItemType == ItemType2HSlash
|| PrimaryWeapon->ItemType == ItemType2HBlunt
|| PrimaryWeapon->ItemType == ItemType2HPiercing)) {
attack_dw_timer.Disable();
continue;
}
}
//clients must have the skill to use it...
if(IsClient()) {
//if we cant dual wield, skip it
if (!CanThisClassDualWield()) {
attack_dw_timer.Disable();
continue;
}
} else {
//NPCs get it for free at 13
if(GetLevel() < 13) {
attack_dw_timer.Disable();
continue;
}
}
}
//see if we have a valid weapon
if(ItemToUse != nullptr) {
//check type and damage/delay
if(ItemToUse->ItemClass != ItemClassCommon
|| ItemToUse->Damage == 0
|| ItemToUse->Delay == 0) {
//no weapon
ItemToUse = nullptr;
}
// Check to see if skill is valid
else if((ItemToUse->ItemType > ItemTypeLargeThrowing) && (ItemToUse->ItemType != ItemTypeMartial) && (ItemToUse->ItemType != ItemType2HPiercing)) {
//no weapon
ItemToUse = nullptr;
}
}
int16 DelayMod = itembonuses.HundredHands + spellbonuses.HundredHands;
if (DelayMod < -99)
DelayMod = -99;
//if we have no weapon..
if (ItemToUse == nullptr) {
//above checks ensure ranged weapons do not fall into here
// Work out if we're a monk
if ((GetClass() == MONK) || (GetClass() == BEASTLORD)) {
//we are a monk, use special delay
int speed = (int)( (GetMonkHandToHandDelay()*(100+DelayMod)/100)*(100.0f+attack_speed)*PermaHaste);
// 1200 seemed too much, with delay 10 weapons available
if(speed < RuleI(Combat, MinHastedDelay)) //lower bound
speed = RuleI(Combat, MinHastedDelay);
TimerToUse->SetAtTrigger(speed, true); // Hand to hand, delay based on level or epic
} else {
//not a monk... using fist, regular delay
int speed = (int)((36 *(100+DelayMod)/100)*(100.0f+attack_speed)*PermaHaste);
if(speed < RuleI(Combat, MinHastedDelay) && IsClient()) //lower bound
speed = RuleI(Combat, MinHastedDelay);
TimerToUse->SetAtTrigger(speed, true); // Hand to hand, non-monk 2/36
}
} else {
//we have a weapon, use its delay
// Convert weapon delay to timer resolution (milliseconds)
//delay * 100
int speed = (int)((ItemToUse->Delay*(100+DelayMod)/100)*(100.0f+attack_speed)*PermaHaste);
if(speed < RuleI(Combat, MinHastedDelay))
speed = RuleI(Combat, MinHastedDelay);
if(ItemToUse && (ItemToUse->ItemType == ItemTypeBow || ItemToUse->ItemType == ItemTypeLargeThrowing))
{
if(IsClient())
{
float max_quiver = 0;
for(int r = EmuConstants::GENERAL_BEGIN; r <= EmuConstants::GENERAL_END; r++)
{
const ItemInst *pi = CastToClient()->GetInv().GetItem(r);
if(!pi)
continue;
if(pi->IsType(ItemClassContainer) && pi->GetItem()->BagType == BagTypeQuiver)
{
float temp_wr = ( pi->GetItem()->BagWR / RuleI(Combat, QuiverWRHasteDiv) );
if(temp_wr > max_quiver)
{
max_quiver = temp_wr;
}
}
}
if(max_quiver > 0)
{
float quiver_haste = 1 / (1 + max_quiver / 100);
speed *= quiver_haste;
}
}
}
TimerToUse->SetAtTrigger(speed, true);
}
if(i == MainPrimary)
PrimaryWeapon = ItemToUse;
}
}
bool Mob::CanThisClassDualWield(void) const {
if(!IsClient()) {
return(GetSkill(SkillDualWield) > 0);
@ -4528,6 +4376,15 @@ void Mob::SpreadVirus(uint16 spell_id, uint16 casterID)
void Mob::RemoveNimbusEffect(int effectid)
{
if (effectid == nimbus_effect1)
nimbus_effect1 = 0;
else if (effectid == nimbus_effect2)
nimbus_effect2 = 0;
else if (effectid == nimbus_effect3)
nimbus_effect3 = 0;
EQApplicationPacket* outapp = new EQApplicationPacket(OP_RemoveNimbusEffect, sizeof(RemoveNimbusEffect_Struct));
RemoveNimbusEffect_Struct* rne = (RemoveNimbusEffect_Struct*)outapp->pBuffer;
rne->spawnid = GetID();
@ -5068,7 +4925,7 @@ void Mob::ClearSpecialAbilities() {
}
}
void Mob::ProcessSpecialAbilities(const std::string str) {
void Mob::ProcessSpecialAbilities(const std::string &str) {
ClearSpecialAbilities();
std::vector<std::string> sp = SplitString(str, '^');

View File

@ -684,6 +684,7 @@ public:
inline bool GetInvul(void) { return invulnerable; }
inline void SetExtraHaste(int Haste) { ExtraHaste = Haste; }
virtual int GetHaste();
inline float GetPermaHaste() { return GetHaste() ? 100.0f / (1.0f + static_cast<float>(GetHaste()) / 100.0f) : 100.0f; }
uint8 GetWeaponDamageBonus(const Item_Struct* Weapon);
uint16 GetDamageTable(SkillUseTypes skillinuse);
@ -842,7 +843,7 @@ public:
void StopSpecialAbilityTimer(int ability);
Timer *GetSpecialAbilityTimer(int ability);
void ClearSpecialAbilities();
void ProcessSpecialAbilities(const std::string str);
void ProcessSpecialAbilities(const std::string &str);
Shielders_Struct shielder[MAX_SHIELDERS];
Trade* trade;
@ -1052,6 +1053,7 @@ protected:
Timer attack_dw_timer;
Timer ranged_timer;
float attack_speed; //% increase/decrease in attack speed (not haste)
int8 attack_delay; //delay between attacks in 10ths of seconds
float slow_mitigation; // Allows for a slow mitigation (100 = 100%, 50% = 50%)
Timer tic_timer;
Timer mana_timer;

View File

@ -248,6 +248,7 @@ NPC::NPC(const NPCType* d, Spawn2* in_respawn, float x, float y, float z, float
delaytimer = false;
combat_event = false;
attack_speed = d->attack_speed;
attack_delay = d->attack_delay;
slow_mitigation = d->slow_mitigation;
EntityList::RemoveNumbers(name);
@ -935,6 +936,8 @@ NPC* NPC::SpawnNPC(const char* spawncommand, float in_x, float in_y, float in_z,
npc_type->WIS = 150;
npc_type->CHA = 150;
npc_type->attack_delay = 30;
npc_type->prim_melee_type = 28;
npc_type->sec_melee_type = 28;

View File

@ -134,7 +134,6 @@ public:
void CalcNPCRegen();
void CalcNPCDamage();
int32 GetActSpellDamage(uint16 spell_id, int32 value, Mob* target = nullptr);
int32 GetActSpellHealing(uint16 spell_id, int32 value, Mob* target = nullptr);
inline void SetSpellFocusDMG(int32 NewSpellFocusDMG) {SpellFocusDMG = NewSpellFocusDMG;}
@ -158,6 +157,7 @@ public:
virtual void InitializeBuffSlots();
virtual void UninitializeBuffSlots();
virtual void SetAttackTimer();
virtual void RangedAttack(Mob* other);
virtual void ThrowingAttack(Mob* other) { }
int32 GetNumberOfAttacks() const { return attack_count; }
@ -388,16 +388,16 @@ public:
inline void SetHealScale(float amt) { healscale = amt; }
inline float GetHealScale() { return healscale; }
uint32 GetSpawnKillCount();
int GetScore();
void SetMerchantProbability(uint8 amt) { probability = amt; }
uint32 GetSpawnKillCount();
int GetScore();
void SetMerchantProbability(uint8 amt) { probability = amt; }
uint8 GetMerchantProbability() { return probability; }
void mod_prespawn(Spawn2 *sp);
int mod_npc_damage(int damage, SkillUseTypes skillinuse, int hand, const Item_Struct* weapon, Mob* other);
void mod_prespawn(Spawn2 *sp);
int mod_npc_damage(int damage, SkillUseTypes skillinuse, int hand, const Item_Struct* weapon, Mob* other);
void mod_npc_killed_merit(Mob* c);
void mod_npc_killed(Mob* oos);
void AISpellsList(Client *c);
void AISpellsList(Client *c);
bool IsRaidTarget() const { return raid_target; };
protected:

View File

@ -165,13 +165,12 @@ void QGlobalCache::LoadByGlobalContext()
LoadBy(query);
}
void QGlobalCache::LoadBy(const std::string query)
void QGlobalCache::LoadBy(const std::string &query)
{
auto results = database.QueryDatabase(query);
if (!results.Success())
return;
for (auto row = results.begin(); row != results.end(); ++row)
AddGlobal(0, QGlobal(std::string(row[0]), atoi(row[1]), atoi(row[2]), atoi(row[3]), row[4], row[5]? atoi(row[5]): 0xFFFFFFFF));
auto results = database.QueryDatabase(query);
if (!results.Success())
return;
for (auto row = results.begin(); row != results.end(); ++row)
AddGlobal(0, QGlobal(std::string(row[0]), atoi(row[1]), atoi(row[2]), atoi(row[3]), row[4], row[5]? atoi(row[5]): 0xFFFFFFFF));
}

View File

@ -42,7 +42,7 @@ public:
void LoadByZoneID(uint32 zoneID); //zone
void LoadByGlobalContext(); //zone
protected:
void LoadBy(const std::string query);
void LoadBy(const std::string &query);
std::list<QGlobal> qGlobalBucket;
};

View File

@ -1413,10 +1413,8 @@ void NPC::DoClassAttacks(Mob *target) {
return;
float HasteModifier = 0;
if(GetHaste() > 0)
if (GetHaste())
HasteModifier = 10000 / (100 + GetHaste());
else if(GetHaste() < 0)
HasteModifier = (100 - GetHaste());
else
HasteModifier = 100;
@ -1464,7 +1462,7 @@ void NPC::DoClassAttacks(Mob *target) {
}
}
reuse = KickReuseTime * 1000;
reuse = (KickReuseTime + 3) * 1000;
DoSpecialAttackDamage(target, SkillKick, dmg, 1, -1, reuse);
did_attack = true;
}
@ -1484,7 +1482,7 @@ void NPC::DoClassAttacks(Mob *target) {
}
}
reuse = BashReuseTime * 1000;
reuse = (BashReuseTime + 3) * 1000;
DoSpecialAttackDamage(target, SkillBash, dmg, 1, -1, reuse);
did_attack = true;
}
@ -1537,7 +1535,7 @@ void NPC::DoClassAttacks(Mob *target) {
}
}
reuse = KickReuseTime * 1000;
reuse = (KickReuseTime + 3) * 1000;
DoSpecialAttackDamage(target, SkillKick, dmg, 1, -1, reuse);
did_attack = true;
}
@ -1562,7 +1560,7 @@ void NPC::DoClassAttacks(Mob *target) {
}
}
reuse = BashReuseTime * 1000;
reuse = (BashReuseTime + 3) * 1000;
DoSpecialAttackDamage(target, SkillBash, dmg, 1, -1, reuse);
did_attack = true;
}

View File

@ -202,7 +202,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial)
effect_value = GetMaxHP();
if (GetSpellPowerDistanceMod())
effect_value = effect_value*(GetSpellPowerDistanceMod()/100);
effect_value = effect_value*GetSpellPowerDistanceMod()/100;
#ifdef SPELL_EFFECT_SPAM
effect_desc[0] = 0;
@ -3127,26 +3127,42 @@ snare has both of them negative, yet their range should work the same:
case 110: // confirmed 2/6/04
//is there a reason we dont use updownsign here???
result = ubase + (caster_level / 5); break;
result = ubase + (caster_level / 6);
break;
case 111:
result = updownsign * (ubase + 6 * (caster_level - GetMinLevel(spell_id))); break;
result = updownsign * (ubase + 6 * (caster_level - 16));
break;
case 112:
result = updownsign * (ubase + 8 * (caster_level - GetMinLevel(spell_id))); break;
result = updownsign * (ubase + 8 * (caster_level - 24));
break;
case 113:
result = updownsign * (ubase + 10 * (caster_level - GetMinLevel(spell_id))); break;
result = updownsign * (ubase + 10 * (caster_level - 34));
break;
case 114:
result = updownsign * (ubase + 15 * (caster_level - GetMinLevel(spell_id))); break;
result = updownsign * (ubase + 15 * (caster_level - 44));
break;
//these formula were updated according to lucy 10/16/04
case 115: // this is only in symbol of transal
result = ubase + 6 * (caster_level - GetMinLevel(spell_id)); break;
result = ubase;
if (caster_level > 15)
result += 7 * (caster_level - 15);
break;
case 116: // this is only in symbol of ryltan
result = ubase + 8 * (caster_level - GetMinLevel(spell_id)); break;
result = ubase;
if (caster_level > 24)
result += 10 * (caster_level - 24);
break;
case 117: // this is only in symbol of pinzarn
result = ubase + 12 * (caster_level - GetMinLevel(spell_id)); break;
result = ubase;
if (caster_level > 34)
result += 13 * (caster_level - 34);
break;
case 118: // used in naltron and a few others
result = ubase + 20 * (caster_level - GetMinLevel(spell_id)); break;
result = ubase;
if (caster_level > 44)
result += 20 * (caster_level - 44);
break;
case 119: // confirmed 2/6/04
result = ubase + (caster_level / 8); break;
@ -3166,6 +3182,93 @@ snare has both of them negative, yet their range should work the same:
result = MakeRandomInt(ubase, abs(max));
break;
case 124: // check sign
result = ubase;
if (caster_level > 50)
result += caster_level - 50;
break;
case 125: // check sign
result = ubase;
if (caster_level > 50)
result += 2 * (caster_level - 50);
break;
case 126: // check sign
result = ubase;
if (caster_level > 50)
result += 3 * (caster_level - 50);
break;
case 127: // check sign
result = ubase;
if (caster_level > 50)
result += 4 * (caster_level - 50);
break;
case 128: // check sign
result = ubase;
if (caster_level > 50)
result += 5 * (caster_level - 50);
break;
case 129: // check sign
result = ubase;
if (caster_level > 50)
result += 10 * (caster_level - 50);
break;
case 130: // check sign
result = ubase;
if (caster_level > 50)
result += 15 * (caster_level - 50);
break;
case 131: // check sign
result = ubase;
if (caster_level > 50)
result += 20 * (caster_level - 50);
break;
case 132: // check sign
result = ubase;
if (caster_level > 50)
result += 25 * (caster_level - 50);
break;
case 137: // used in berserker AA desperation
result = ubase - (ubase * (GetHPRatio() / 100.0f));
break;
case 138: { // unused on live?
int maxhps = GetMaxHP() / 2;
if (GetHP() <= maxhps)
result = -(ubase * GetHP() / maxhps);
else
result = -ubase;
break;
}
case 139: // check sign
result = ubase + (caster_level > 30 ? (caster_level - 30) / 2 : 0);
break;
case 140: // check sign
result = ubase + (caster_level > 30 ? caster_level - 30 : 0);
break;
case 141: // check sign
result = ubase + (caster_level > 30 ? (3 * caster_level - 90) / 2 : 0);
break;
case 142: // check sign
result = ubase + (caster_level > 30 ? 2 * caster_level - 60 : 0);
break;
case 143: // check sign
result = ubase + (3 * caster_level / 4);
break;
//these are used in stacking effects... formula unknown
case 201:
case 203:
@ -4016,6 +4119,9 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses)
if (!found_numhits)
Numhits(false);
}
if (spells[buffs[slot].spellid].NimbusEffect > 0)
RemoveNimbusEffect(spells[buffs[slot].spellid].NimbusEffect);
buffs[slot].spellid = SPELL_UNKNOWN;
if(IsPet() && GetOwner() && GetOwner()->IsClient()) {

View File

@ -4501,13 +4501,12 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
}
else
{
resist_chance -= roll;
if(resist_chance < 1)
{
resist_chance = 1;
}
int partial_modifier = ((150 * (roll - resist_chance)) / resist_chance);
int partial_modifier = ((150 * (resist_chance - roll)) / resist_chance);
if(IsNPC())
{
@ -4535,17 +4534,16 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
}
}
if(partial_modifier < 0)
if(partial_modifier <= 0)
{
return 100;
}
else if(partial_modifier >= 100)
{
return 0;
}
if(partial_modifier > 100)
{
return 100;
}
return partial_modifier;
return (100.0f - partial_modifier);
}
}
}

View File

@ -31,25 +31,17 @@ bool TitleManager::LoadTitles()
{
Titles.clear();
TitleEntry Title;
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = nullptr;
MYSQL_RES *result;
MYSQL_ROW row;
if (!database.RunQuery(query, MakeAnyLenString(&query,
"SELECT `id`, `skill_id`, `min_skill_value`, `max_skill_value`, `min_aa_points`, `max_aa_points`, `class`, `gender`, "
"`char_id`, `status`, `item_id`, `prefix`, `suffix`, `title_set` from titles"), errbuf, &result))
{
LogFile->write(EQEMuLog::Error, "Unable to load titles: %s : %s", query, errbuf);
safe_delete_array(query);
return(false);
std::string query = "SELECT `id`, `skill_id`, `min_skill_value`, `max_skill_value`, "
"`min_aa_points`, `max_aa_points`, `class`, `gender`, `char_id`, "
"`status`, `item_id`, `prefix`, `suffix`, `title_set` FROM titles";
auto results = database.QueryDatabase(query);
if (!results.Success()) {
LogFile->write(EQEMuLog::Error, "Unable to load titles: %s : %s", query.c_str(), results.ErrorMessage().c_str());
return false;
}
safe_delete_array(query);
while ((row = mysql_fetch_row(result))) {
for (auto row = results.begin(); row != results.end(); ++row) {
TitleEntry Title;
Title.TitleID = atoi(row[0]);
Title.SkillID = (SkillUseTypes) atoi(row[1]);
Title.MinSkillValue = atoi(row[2]);
@ -66,9 +58,8 @@ bool TitleManager::LoadTitles()
Title.TitleSet = atoi(row[13]);
Titles.push_back(Title);
}
mysql_free_result(result);
return(true);
return true;
}
EQApplicationPacket *TitleManager::MakeTitlesPacket(Client *c)
@ -244,92 +235,70 @@ bool TitleManager::IsNewTradeSkillTitleAvailable(int SkillID, int SkillValue)
return false;
}
void TitleManager::CreateNewPlayerTitle(Client *c, const char *Title)
void TitleManager::CreateNewPlayerTitle(Client *client, const char *title)
{
if(!c || !Title)
if(!client || !title)
return;
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = nullptr;
MYSQL_RES *result;
char *escTitle = new char[strlen(title) * 2 + 1];
char *EscTitle = new char[strlen(Title) * 2 + 1];
client->SetAATitle(title);
c->SetAATitle(Title);
database.DoEscapeString(EscTitle, Title, strlen(Title));
if (database.RunQuery(query, MakeAnyLenString(&query,
"SELECT `id` from titles where `prefix` = '%s' and char_id = %i", EscTitle, c->CharacterID()), errbuf, &result))
{
if(mysql_num_rows(result) > 0)
{
mysql_free_result(result);
safe_delete_array(query);
safe_delete_array(EscTitle);
return;
}
mysql_free_result(result);
database.DoEscapeString(escTitle, title, strlen(title));
auto query = StringFormat("SELECT `id` FROM titles "
"WHERE `prefix` = '%s' AND char_id = %i",
escTitle, client->CharacterID());
auto results = database.QueryDatabase(query);
if (results.Success() && results.RowCount() > 0){
safe_delete_array(escTitle);
return;
}
safe_delete_array(query);
if(!database.RunQuery(query,MakeAnyLenString(&query, "INSERT into titles (`char_id`, `prefix`) VALUES(%i, '%s')",
c->CharacterID(), EscTitle), errbuf))
LogFile->write(EQEMuLog::Error, "Error adding title: %s %s", query, errbuf);
else
{
ServerPacket* pack = new ServerPacket(ServerOP_ReloadTitles, 0);
worldserver.SendPacket(pack);
safe_delete(pack);
}
safe_delete_array(query);
safe_delete_array(EscTitle);
query = StringFormat("INSERT INTO titles (`char_id`, `prefix`) VALUES(%i, '%s')",
client->CharacterID(), escTitle);
safe_delete_array(escTitle);
results = database.QueryDatabase(query);
if(!results.Success()) {
LogFile->write(EQEMuLog::Error, "Error adding title: %s %s", query.c_str(), results.ErrorMessage().c_str());
return;
}
ServerPacket* pack = new ServerPacket(ServerOP_ReloadTitles, 0);
worldserver.SendPacket(pack);
safe_delete(pack);
}
void TitleManager::CreateNewPlayerSuffix(Client *c, const char *Suffix)
void TitleManager::CreateNewPlayerSuffix(Client *client, const char *suffix)
{
if(!c || !Suffix)
if(!client || !suffix)
return;
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = nullptr;
MYSQL_RES *result;
client->SetTitleSuffix(suffix);
char *EscSuffix = new char[strlen(Suffix) * 2 + 1];
char *escSuffix = new char[strlen(suffix) * 2 + 1];
database.DoEscapeString(escSuffix, suffix, strlen(suffix));
c->SetTitleSuffix(Suffix);
database.DoEscapeString(EscSuffix, Suffix, strlen(Suffix));
if (database.RunQuery(query, MakeAnyLenString(&query,
"SELECT `id` from titles where `suffix` = '%s' and char_id = %i", EscSuffix, c->CharacterID()), errbuf, &result))
{
if(mysql_num_rows(result) > 0)
{
mysql_free_result(result);
safe_delete_array(query);
safe_delete_array(EscSuffix);
std::string query = StringFormat("SELECT `id` FROM titles "
"WHERE `suffix` = '%s' AND char_id = %i",
escSuffix, client->CharacterID());
auto results = database.QueryDatabase(query);
if (results.Success() && results.RowCount() > 0) {
safe_delete_array(escSuffix);
return;
}
mysql_free_result(result);
}
}
safe_delete_array(query);
if(!database.RunQuery(query,MakeAnyLenString(&query, "INSERT into titles (`char_id`, `suffix`) VALUES(%i, '%s')",
c->CharacterID(), EscSuffix), errbuf))
LogFile->write(EQEMuLog::Error, "Error adding title suffix: %s %s", query, errbuf);
else
{
ServerPacket* pack = new ServerPacket(ServerOP_ReloadTitles, 0);
worldserver.SendPacket(pack);
safe_delete(pack);
}
safe_delete_array(query);
safe_delete_array(EscSuffix);
query = StringFormat("INSERT INTO titles (`char_id`, `suffix`) VALUES(%i, '%s')",
client->CharacterID(), escSuffix);
safe_delete_array(escSuffix);
results = database.QueryDatabase(query);
if(!results.Success()) {
LogFile->write(EQEMuLog::Error, "Error adding title suffix: %s %s", query.c_str(), results.ErrorMessage().c_str());
return;
}
ServerPacket* pack = new ServerPacket(ServerOP_ReloadTitles, 0);
worldserver.SendPacket(pack);
safe_delete(pack);
}
void Client::SetAATitle(const char *Title)
@ -368,67 +337,48 @@ void Client::SetTitleSuffix(const char *Suffix)
safe_delete(outapp);
}
void Client::EnableTitle(int titleset) {
void Client::EnableTitle(int titleSet) {
if (CheckTitle(titleset)) {
if (CheckTitle(titleSet))
return;
}
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
std::string query = StringFormat("INSERT INTO player_titlesets "
"(char_id, title_set) VALUES (%i, %i)",
CharacterID(), titleSet);
auto results = database.QueryDatabase(query);
if(!results.Success())
LogFile->write(EQEMuLog::Error, "Error in EnableTitle query for titleset %i and charid %i", titleSet, CharacterID());
if(!database.RunQuery(query,MakeAnyLenString(&query, "INSERT INTO player_titlesets (char_id, title_set) VALUES (%i, %i)", CharacterID(), titleset), errbuf)) {
LogFile->write(EQEMuLog::Error, "Error in EnableTitle query for titleset %i and charid %i", titleset, CharacterID());
safe_delete_array(query);
return;
}
else {
safe_delete_array(query);
return;
}
}
bool Client::CheckTitle(int titleset) {
bool Client::CheckTitle(int titleSet) {
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
MYSQL_RES *result;
if (database.RunQuery(query, MakeAnyLenString(&query, "SELECT `id` FROM player_titlesets WHERE `title_set`=%i AND `char_id`=%i LIMIT 1", titleset, CharacterID()), errbuf, &result)) {
safe_delete_array(query);
if (mysql_num_rows(result) >= 1) {
mysql_free_result(result);
return(true);
}
mysql_free_result(result);
std::string query = StringFormat("SELECT `id` FROM player_titlesets "
"WHERE `title_set`=%i AND `char_id`=%i LIMIT 1",
titleSet, CharacterID());
auto results = database.QueryDatabase(query);
if (!results.Success()) {
LogFile->write(EQEMuLog::Error, "Error in CheckTitle query '%s': %s", query.c_str(), results.ErrorMessage().c_str());
return false;
}
else {
LogFile->write(EQEMuLog::Error, "Error in CheckTitle query '%s': %s", query, errbuf);
safe_delete_array(query);
}
if (results.RowCount() == 0)
return false;
return(false);
return true;
}
void Client::RemoveTitle(int titleset) {
void Client::RemoveTitle(int titleSet) {
if (!CheckTitle(titleset)) {
if (!CheckTitle(titleSet))
return;
}
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
std::string query = StringFormat("DELETE FROM player_titlesets "
"WHERE `title_set` = %i AND `char_id` = %i",
titleSet, CharacterID());
auto results = database.QueryDatabase(query);
if (!results.Success())
LogFile->write(EQEMuLog::Error, "Error in RemoveTitle query '%s': %s", query.c_str(), results.ErrorMessage().c_str());
if (database.RunQuery(query, MakeAnyLenString(&query, "DELETE FROM player_titlesets WHERE `title_set`=%i AND `char_id`=%i", titleset, CharacterID()), errbuf)) {
safe_delete_array(query);
}
else {
LogFile->write(EQEMuLog::Error, "Error in RemoveTitle query '%s': %s", query, errbuf);
safe_delete_array(query);
}
return;
}

View File

@ -567,7 +567,7 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, float speed, b
{
Map::Vertex dest(x_pos, y_pos, z_pos);
float newz = zone->zonemap->FindBestZ(dest, nullptr);
float newz = zone->zonemap->FindBestZ(dest, nullptr) + 2.0f;
mlog(AI__WAYPOINTS, "BestZ returned %4.3f at %4.3f, %4.3f, %4.3f", newz,x_pos,y_pos,z_pos);
@ -696,7 +696,7 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, float speed, b
{
Map::Vertex dest(x_pos, y_pos, z_pos);
float newz = zone->zonemap->FindBestZ(dest, nullptr);
float newz = zone->zonemap->FindBestZ(dest, nullptr); + 2.0f;
mlog(AI__WAYPOINTS, "BestZ returned %4.3f at %4.3f, %4.3f, %4.3f", newz,x_pos,y_pos,z_pos);
@ -821,7 +821,7 @@ bool Mob::CalculateNewPosition(float x, float y, float z, float speed, bool chec
{
Map::Vertex dest(x_pos, y_pos, z_pos);
float newz = zone->zonemap->FindBestZ(dest, nullptr);
float newz = zone->zonemap->FindBestZ(dest, nullptr) + 2.0f;
mlog(AI__WAYPOINTS, "BestZ returned %4.3f at %4.3f, %4.3f, %4.3f", newz,x_pos,y_pos,z_pos);
@ -1007,26 +1007,19 @@ void Mob::SendToFixZ(float new_x, float new_y, float new_z) {
}
int ZoneDatabase::GetHighestGrid(uint32 zoneid) {
char *query = 0;
char errbuff[MYSQL_ERRMSG_SIZE];
MYSQL_RES *result;
MYSQL_ROW row;
int res = 0;
if (RunQuery(query, MakeAnyLenString(&query,
"SELECT COALESCE(MAX(id), 0) FROM grid WHERE zoneid = %i",
zoneid),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 GetHighestGrid query '%s': %s", query, errbuff);
safe_delete_array(query);
}
return(res);
std::string query = StringFormat("SELECT COALESCE(MAX(id), 0) FROM grid WHERE zoneid = %i", zoneid);
auto results = QueryDatabase(query);
if (!results.Success()) {
LogFile->write(EQEMuLog::Error, "Error in GetHighestGrid 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]);
}
uint8 ZoneDatabase::GetGridType2(uint32 grid, uint16 zoneid) {

File diff suppressed because it is too large Load Diff

View File

@ -111,6 +111,7 @@ struct NPCType
uint8 spawn_limit; //only this many may be in zone at a time (0=no limit)
uint8 mount_color; //only used by horse class
float attack_speed; //%+- on attack delay of the mob.
uint8 attack_delay; //delay between attacks in 10ths of a second
int accuracy_rating; //10 = 1% accuracy
int avoidance_rating; //10 = 1% avoidance
bool findable; //can be found with find command