- Fix #549 - DoF inventory can now remove/add items freely. The lower bounds index <=255 is assigned to existing items on zone-in or replacing items in that existing index. Indexed items 256+ will represent new items that have to grow the clients item index array. This will distinguish properly what the client is trying to examine when requests come in (versus getting no index).

- Fixed server crashing trying to send spawn packets when handling a bad version of the client entering zone (or malformed packets causing bad versioning).
- Character History is now only saved on requirement, reduced DB load.
- Avoidance of reuse for Query class as it can cause crashes
- Fixed a potential issue with AoM/DoF inventory, in which it would continually build up the array instead of re-using the first slot, eventually causing the client to crash
This commit is contained in:
Emagi 2023-12-10 08:20:29 -05:00
parent 480c61f7e5
commit e10aedb79d
11 changed files with 213 additions and 99 deletions

View file

@ -257,7 +257,7 @@ void MasterAAList::DisplayAA(Client* client,int8 newtemplate,int8 changemode) {
if (TreeNodeList.size() == 0)
return;
vector<vector<vector<AAEntry> > > AAEntryList ;
Query query;
Query query, query2;
MYSQL_ROW row;
int32 Pid = client->GetCharacterID();
@ -282,7 +282,7 @@ void MasterAAList::DisplayAA(Client* client,int8 newtemplate,int8 changemode) {
}
LogWrite(SPELL__INFO, 0, "AA", "Loaded %u AA Tree Nodes", AAEntryList.size());
// load tmplates 4-6 Server
MYSQL_RES* result2 = query.RunQuery2(Q_SELECT, "SELECT `template_id`,`tab_id`,`aa_id`,`order`,treeid FROM character_aa where char_id = %i order by `order`", client->GetCharacterID());
MYSQL_RES* result2 = query2.RunQuery2(Q_SELECT, "SELECT `template_id`,`tab_id`,`aa_id`,`order`,treeid FROM character_aa where char_id = %i order by `order`", client->GetCharacterID());
while (result2 && (row = mysql_fetch_row(result2))) {
AAEntry newentry;

View file

@ -2095,9 +2095,19 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
if(sep && sep->arg[1][0] && sep->IsNumber(1)){
if(strcmp(sep->arg[0], "inventory") == 0){
int32 item_index = atol(sep->arg[1]);
//printf("Index provided: %u\n",item_index);
if(client->GetVersion() <= 546 && item_index <= 255) {
item_index = 255 - item_index;
if(client->GetVersion() <= 546) {
if(item_index <= 255) {
item_index = 255 - item_index;
}
else {
if(item_index == 256) { // first "new" item to inventory is assigned index 256 by client
item_index = client->GetPlayer()->item_list.GetFirstNewItem();
}
else {
// otherwise the slot has to be mapped out depending on the amount of new items + index sent in
item_index = client->GetPlayer()->item_list.GetNewItemByIndex((int16)item_index - 255);
}
}
}
Item* item = client->GetPlayer()->item_list.GetItemFromIndex(item_index);
if(item){

View file

@ -118,7 +118,7 @@ bool WorldDatabase::RemoveSpawnTemplate(int32 template_id)
int32 WorldDatabase::CreateSpawnFromTemplateByID(Client* client, int32 template_id)
{
Query query;
Query query, query2, query3, query4, query5, query6;
MYSQL_ROW row;
int32 spawn_location_id = 0;
float new_x = client->GetPlayer()->GetX();
@ -130,8 +130,7 @@ int32 WorldDatabase::CreateSpawnFromTemplateByID(Client* client, int32 template_
LogWrite(COMMAND__DEBUG, 5, "Command", "\tCoords: %.2f %.2f %.2f...", new_x, new_y, new_z);
// find the spawn_location_id in the template we plan to duplicate
Query query1;
MYSQL_RES* result = query1.RunQuery2(Q_SELECT, "SELECT spawn_location_id FROM spawn_templates WHERE id = %u", template_id);
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT spawn_location_id FROM spawn_templates WHERE id = %u", template_id);
if (result && (row = mysql_fetch_row(result))) {
if (row[0])
spawn_location_id = atoi(row[0]);
@ -143,26 +142,25 @@ int32 WorldDatabase::CreateSpawnFromTemplateByID(Client* client, int32 template_
// insert a new spawn_location_name record
string name = "TemplateGenerated";
query.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_name (name) VALUES ('%s')", name.c_str());
if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query.GetQuery(), query.GetError());
query2.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_name (name) VALUES ('%s')", name.c_str());
if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query2.GetQuery(), query2.GetError());
return 0;
}
int32 new_location_id = query.GetLastInsertedID();
int32 new_location_id = query2.GetLastInsertedID();
LogWrite(COMMAND__DEBUG, 5, "Command", "Created new Spawn Location: '%s' (%u)", name.c_str(), new_location_id);
// get all spawn_location_entries that match the templates spawn_location_id value and insert as new
LogWrite(COMMAND__DEBUG, 5, "Command", "Finding existing spawn_location_entry(s) for location_id %u", spawn_location_id);
Query query2;
MYSQL_RES* result2 = query2.RunQuery2(Q_SELECT, "SELECT spawn_id, spawnpercentage FROM spawn_location_entry WHERE spawn_location_id = %u", spawn_location_id);
MYSQL_RES* result2 = query3.RunQuery2(Q_SELECT, "SELECT spawn_id, spawnpercentage FROM spawn_location_entry WHERE spawn_location_id = %u", spawn_location_id);
if(result2 && mysql_num_rows(result2) > 0){
MYSQL_ROW row2;
while(result2 && (row2 = mysql_fetch_row(result2)) && row2[0])
{
query.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_entry (spawn_id, spawn_location_id, spawnpercentage) VALUES (%u, %u, %i)",
query4.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_entry (spawn_id, spawn_location_id, spawnpercentage) VALUES (%u, %u, %i)",
atoul(row2[0]), new_location_id, atoi(row2[1]));
if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query.GetQuery(), query.GetError());
if(query4.GetErrorNumber() && query4.GetError() && query4.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query4.GetQuery(), query4.GetError());
return 0;
}
LogWrite(COMMAND__DEBUG, 5, "Command", "Insert Entry for spawn_id %u, location_id %u, percentage %i", atoul(row2[0]), new_location_id, atoi(row2[1]));
@ -172,16 +170,15 @@ int32 WorldDatabase::CreateSpawnFromTemplateByID(Client* client, int32 template_
// get all spawn_location_placements that match the templates spawn_location_id value and insert as new
// Note: /spawn templates within current zone_id only, because of spawn_id issues (cannot template an Antonic spawn in Commonlands)
LogWrite(COMMAND__DEBUG, 5, "Command", "Finding existing spawn_location_placement(s) for location_id %u", spawn_location_id);
Query query3;
MYSQL_RES* result3 = query3.RunQuery2(Q_SELECT, "SELECT zone_id, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id FROM spawn_location_placement WHERE spawn_location_id = %u", spawn_location_id);
MYSQL_RES* result3 = query5.RunQuery2(Q_SELECT, "SELECT zone_id, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id FROM spawn_location_placement WHERE spawn_location_id = %u", spawn_location_id);
if(result3 && mysql_num_rows(result3) > 0){
MYSQL_ROW row3;
while(result3 && (row3 = mysql_fetch_row(result3)) && row3[0])
{
query.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_placement (zone_id, spawn_location_id, x, y, z, heading, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id) VALUES (%i, %u, %2f, %2f, %2f, %2f, %2f, %2f, %2f, %i, %i, %i, %u)",
query6.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_placement (zone_id, spawn_location_id, x, y, z, heading, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id) VALUES (%i, %u, %2f, %2f, %2f, %2f, %2f, %2f, %2f, %i, %i, %i, %u)",
atoi(row3[0]), new_location_id, new_x, new_y, new_z, new_heading, atof(row3[1]), atof(row3[2]), atof(row3[3]), atoi(row3[4]), atoi(row3[5]), atoi(row3[6]), atoul(row3[7]));
if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query.GetQuery(), query.GetError());
if(query6.GetErrorNumber() && query6.GetError() && query6.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query6.GetQuery(), query6.GetError());
return 0;
}
LogWrite(COMMAND__DEBUG, 5, "Command", "Insert Placement at new coords for location_id %u", new_location_id);
@ -197,7 +194,7 @@ int32 WorldDatabase::CreateSpawnFromTemplateByID(Client* client, int32 template_
int32 WorldDatabase::CreateSpawnFromTemplateByName(Client* client, const char* template_name)
{
Query query;
Query query, query1, query2, query3, query4, query5, query6;
MYSQL_ROW row;
int32 template_id = 0;
int32 spawn_location_id = 0;
@ -210,8 +207,7 @@ int32 WorldDatabase::CreateSpawnFromTemplateByName(Client* client, const char* t
LogWrite(COMMAND__DEBUG, 5, "Command", "\tCoords: %.2f %.2f %.2f...", new_x, new_y, new_z);
// find the spawn_location_id in the template we plan to duplicate
Query query1;
MYSQL_RES* result = query1.RunQuery2(Q_SELECT, "SELECT id, spawn_location_id FROM spawn_templates WHERE name = '%s'", template_name);
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, spawn_location_id FROM spawn_templates WHERE name = '%s'", template_name);
if (result && (row = mysql_fetch_row(result))) {
if (row[0])
{
@ -226,26 +222,25 @@ int32 WorldDatabase::CreateSpawnFromTemplateByName(Client* client, const char* t
// insert a new spawn_location_name record
string name = "TemplateGenerated";
query.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_name (name) VALUES ('%s')", name.c_str());
if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query.GetQuery(), query.GetError());
query2.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_name (name) VALUES ('%s')", name.c_str());
if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query2.GetQuery(), query2.GetError());
return 0;
}
int32 new_location_id = query.GetLastInsertedID();
int32 new_location_id = query2.GetLastInsertedID();
LogWrite(COMMAND__DEBUG, 5, "Command", "Created new Spawn Location: '%s' (%u)", name.c_str(), new_location_id);
// get all spawn_location_entries that match the templates spawn_location_id value and insert as new
LogWrite(COMMAND__DEBUG, 5, "Command", "Finding existing spawn_location_entry(s) for location_id %u", spawn_location_id);
Query query2;
MYSQL_RES* result2 = query2.RunQuery2(Q_SELECT, "SELECT spawn_id, spawnpercentage FROM spawn_location_entry WHERE spawn_location_id = %u", spawn_location_id);
MYSQL_RES* result2 = query3.RunQuery2(Q_SELECT, "SELECT spawn_id, spawnpercentage FROM spawn_location_entry WHERE spawn_location_id = %u", spawn_location_id);
if(result2 && mysql_num_rows(result2) > 0){
MYSQL_ROW row2;
while(result2 && (row2 = mysql_fetch_row(result2)) && row2[0])
{
query.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_entry (spawn_id, spawn_location_id, spawnpercentage) VALUES (%u, %u, %i)",
query4.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_entry (spawn_id, spawn_location_id, spawnpercentage) VALUES (%u, %u, %i)",
atoul(row2[0]), new_location_id, atoi(row2[1]));
if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query.GetQuery(), query.GetError());
if(query4.GetErrorNumber() && query4.GetError() && query4.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query4.GetQuery(), query4.GetError());
return 0;
}
LogWrite(COMMAND__DEBUG, 5, "Command", "Insert Entry for spawn_id %u, location_id %u, percentage %i", atoul(row2[0]), new_location_id, atoi(row2[1]));
@ -255,16 +250,15 @@ int32 WorldDatabase::CreateSpawnFromTemplateByName(Client* client, const char* t
// get all spawn_location_placements that match the templates spawn_location_id value and insert as new
// Note: /spawn templates within current zone_id only, because of spawn_id issues (cannot template an Antonic spawn in Commonlands)
LogWrite(COMMAND__DEBUG, 5, "Command", "Finding existing spawn_location_placement(s) for location_id %u", spawn_location_id);
Query query3;
MYSQL_RES* result3 = query3.RunQuery2(Q_SELECT, "SELECT zone_id, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id FROM spawn_location_placement WHERE spawn_location_id = %u", spawn_location_id);
MYSQL_RES* result3 = query5.RunQuery2(Q_SELECT, "SELECT zone_id, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id FROM spawn_location_placement WHERE spawn_location_id = %u", spawn_location_id);
if(result3 && mysql_num_rows(result3) > 0){
MYSQL_ROW row3;
while(result3 && (row3 = mysql_fetch_row(result3)) && row3[0])
{
query.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_placement (zone_id, spawn_location_id, x, y, z, heading, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id) VALUES (%i, %u, %2f, %2f, %2f, %2f, %2f, %2f, %2f, %i, %i, %i, %u)",
query6.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_placement (zone_id, spawn_location_id, x, y, z, heading, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id) VALUES (%i, %u, %2f, %2f, %2f, %2f, %2f, %2f, %2f, %i, %i, %i, %u)",
atoi(row3[0]), new_location_id, new_x, new_y, new_z, new_heading, atof(row3[1]), atof(row3[2]), atof(row3[3]), atoi(row3[4]), atoi(row3[5]), atoi(row3[6]), atoul(row3[7]));
if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query.GetQuery(), query.GetError());
if(query6.GetErrorNumber() && query6.GetError() && query6.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query6.GetQuery(), query6.GetError());
return 0;
}
LogWrite(COMMAND__DEBUG, 5, "Command", "Insert Placement at new coords for location_id %u", new_location_id);

View file

@ -2946,13 +2946,20 @@ bool PlayerItemList::AddItem(Item* item){ //is called with a slot already set
if(itr->first > max_index) //just grab the highest index val for next loop
max_index = itr->first;
}
bool doNotOverrideIndex = false;
for(int32 i=0;i<max_index;i++){
if(!indexed_items[i]){
new_index = i;
LogWrite(ITEM__DEBUG, 9, "Item %s assigned to %u",item->name.c_str(), i);
item->details.new_item = false;
item->details.new_index = 0;
doNotOverrideIndex = true;
break;
}
}
}
if(new_index == 0 && max_index > 0)
// may break non DoF clients
if(!doNotOverrideIndex && new_index == 0 && max_index > 0)
new_index = max_index;
indexed_items[new_index] = item;
@ -3257,6 +3264,8 @@ bool PlayerItemList::AssignItemToFreeSlot(Item* item){
if(items[bag->details.bag_id][BASE_EQUIPMENT].count(x) == 0){
item->details.inv_slot_id = bag->details.bag_id;
item->details.slot_id = x;
item->details.new_item = true;
item->details.new_index = 0;
MPlayerItems.releasewritelock(__FUNCTION__, __LINE__);
bool ret = AddItem(item);
return ret;
@ -3270,6 +3279,8 @@ bool PlayerItemList::AssignItemToFreeSlot(Item* item){
if(items[0][BASE_EQUIPMENT].count(i) == 0){
item->details.inv_slot_id = 0;
item->details.slot_id = i;
item->details.new_item = true;
item->details.new_index = 0;
MPlayerItems.releasewritelock(__FUNCTION__, __LINE__);
bool ret = AddItem(item);
return ret;
@ -3505,10 +3516,14 @@ EQ2Packet* PlayerItemList::serialize(Player* player, int16 version){
packet_count = size;
int16 new_index = 0;
for(int16 i = 0; i < indexed_items.size(); i++){
item = indexed_items[i];
if(item && item->details.new_item)
new_index++;
if (item && item->details.item_id > 0)
AddItemToPacket(packet, player, item, i);
AddItemToPacket(packet, player, item, i, false, new_index);
}
if (overflowItems.size() > 0) {
@ -3535,7 +3550,34 @@ EQ2Packet* PlayerItemList::serialize(Player* player, int16 version){
return app;
}
void PlayerItemList::AddItemToPacket(PacketStruct* packet, Player* player, Item* item, int16 i, bool overflow){
int16 PlayerItemList::GetFirstNewItem() {
int16 new_item_slot = 0;
for(int16 i = 0; i < indexed_items.size(); i++){
Item* item = indexed_items[i];
if(item && item->details.new_item) {
return i;
}
}
return 0xFFFF;
}
int16 PlayerItemList::GetNewItemByIndex(int16 in_index) {
int16 new_item_slot = 0;
for(int16 i = 0; i < indexed_items.size(); i++){
Item* item = indexed_items[i];
if(item && item->details.new_item) {
new_item_slot++;
int16 actual_index = in_index - new_item_slot;
LogWrite(ITEM__DEBUG, 9, "In index: %u new index %u actual %u and %u, new slot num %u", in_index, item->details.new_index, actual_index, i, new_item_slot);
if(actual_index == i) {
return i;
}
}
}
return 0xFFFF;
}
void PlayerItemList::AddItemToPacket(PacketStruct* packet, Player* player, Item* item, int16 i, bool overflow, int16 new_index){
Client *client;
if (!packet || !player)
return;
@ -3667,7 +3709,14 @@ void PlayerItemList::AddItemToPacket(PacketStruct* packet, Player* player, Item*
/* DoF client and earlier side automatically assigns indexes
** we have to send 0xFF or else all index is set to 255 on client
** and then examine inventory won't work */
packet->setSubstructArrayDataByName("items", "index", 0xFF, 0, i);
LogWrite(ITEM__DEBUG, 9, "%s Offset index %u bag id %u (new index %u, set index %u)",item->name.c_str(),i, item->details.bag_id, new_index, item->details.new_index);
if(item->details.new_item) {
item->details.new_index = new_index + i; // we have to offset in this way to get consistent indexes for the client to send back
packet->setSubstructArrayDataByName("items", "index", 0xFF+item->details.new_index, 0, i);
}
else {
packet->setSubstructArrayDataByName("items", "index", 0xFF, 0, i);
}
}
else {
packet->setSubstructArrayDataByName("items", "index", i, 0, i);

View file

@ -706,6 +706,8 @@ struct ItemCore{
int8 num_free_slots;
int16 recommended_level;
bool item_locked;
bool new_item;
int16 new_index;
};
#pragma pack()
struct ItemStat{
@ -1139,9 +1141,12 @@ public:
int32 GetItemCountInBag(Item* bag);
int16 GetFirstNewItem();
int16 GetNewItemByIndex(int16 in_index);
Mutex MPlayerItems;
private:
void AddItemToPacket(PacketStruct* packet, Player* player, Item* item, int16 i, bool overflow = false);
void AddItemToPacket(PacketStruct* packet, Player* player, Item* item, int16 i, bool overflow = false, int16 new_index = 0);
void Stack(Item* orig_item, Item* item);
int16 packet_count;
vector<Item*> overflowItems;

View file

@ -1250,7 +1250,6 @@ void WorldDatabase::SaveItem(int32 account_id, int32 char_id, Item* item, const
void WorldDatabase::DeleteItem(int32 char_id, Item* item, const char* type)
{
Query query;
string delete_item;
if(type)
@ -1258,6 +1257,7 @@ void WorldDatabase::DeleteItem(int32 char_id, Item* item, const char* type)
LogWrite(ITEM__DEBUG, 1, "Items", "Deleting item_id %u (Type: %s) for player %u", item->details.item_id, type, char_id);
delete_item = string("DELETE FROM character_items WHERE char_id = %u AND (id = %u OR bag_id = %u) AND type='%s'");
Query query;
query.RunQuery2(Q_DELETE, delete_item.c_str(), char_id, item->details.unique_id, item->details.unique_id, type);
}
else
@ -1265,12 +1265,14 @@ void WorldDatabase::DeleteItem(int32 char_id, Item* item, const char* type)
LogWrite(ITEM__DEBUG, 0, "Items", "Deleting item_id %u for player %u", item->details.item_id, char_id);
delete_item = string("DELETE FROM character_items WHERE char_id = %u AND (id = %u OR bag_id = %u)");
query.RunQuery2(Q_DELETE, delete_item.c_str(), char_id, item->details.unique_id, item->details.unique_id);
Query query2;
query2.RunQuery2(Q_DELETE, delete_item.c_str(), char_id, item->details.unique_id, item->details.unique_id);
}
if(item->CheckFlag2(HEIRLOOM)) {
delete_item = string("DELETE FROM character_items_group_members WHERE unique_id = %u");
query.RunQuery2(Q_DELETE, delete_item.c_str(), item->details.unique_id);
Query query3;
query3.RunQuery2(Q_DELETE, delete_item.c_str(), item->details.unique_id);
}
}
@ -1352,6 +1354,8 @@ void WorldDatabase::LoadCharacterItemList(int32 account_id, int32 char_id, Playe
}
item->details.inv_slot_id = atol(row[10]); //bag_id
item->details.new_item = false;
item->details.new_index = 0;
item->details.count = atoi(row[11]); //count
item->SetMaxSellValue(atoul(row[12])); //max sell value
item->no_sale = (atoul(row[13]) == 1);

View file

@ -6306,6 +6306,7 @@ void Player::HandleHistoryDiscovery(int8 subtype, int32 value, int32 value2) {
hd->Value2 = value2;
hd->EventDate = Timer::GetUnixTimeStamp();
strcpy(hd->Location, GetZone()->GetZoneName());
hd->needs_save = true;
m_characterHistory[HISTORY_TYPE_DISCOVERY][HISTORY_SUBTYPE_LOCATION].push_back(hd);
break;
@ -6326,6 +6327,7 @@ void Player::HandleHistoryXP(int8 subtype, int32 value, int32 value2) {
hd->Value2 = value2;
hd->EventDate = Timer::GetUnixTimeStamp();
strcpy(hd->Location, GetZone()->GetZoneName());
hd->needs_save = true;
m_characterHistory[HISTORY_TYPE_XP][HISTORY_SUBTYPE_ADVENTURE].push_back(hd);
}
@ -6359,7 +6361,11 @@ void Player::SaveHistory() {
for (itr = m_characterHistory.begin(); itr != m_characterHistory.end(); itr++) {
for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) {
for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++) {
database.SaveCharacterHistory(this, itr->first, itr2->first, (*itr3)->Value, (*itr3)->Value2, (*itr3)->Location, (*itr3)->EventDate);
if((*itr3)->needs_save) {
database.SaveCharacterHistory(this, itr->first, itr2->first, (*itr3)->Value, (*itr3)->Value2, (*itr3)->Location, (*itr3)->EventDate);
(*itr3)->needs_save = false;
}
}
}
}

View file

@ -154,6 +154,7 @@ struct HistoryData {
char Location[200];
int32 EventID;
int32 EventDate;
bool needs_save;
};
/// <summary>History set through the LUA system</summary>

View file

@ -685,12 +685,15 @@ EQ2Packet* Spawn::spawn_serialize(Player* player, int16 version, int16 offset, i
ptr += sizeof(oversized_packet);
int16 opcode = 0;
if(IsWidget())
opcode = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqCreateWidgetCmd);
else if(IsSign())
opcode = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqCreateSignWidgetCmd);
else
opcode = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqCreateGhostCmd);
if (EQOpcodeManager.count(GetOpcodeVersion(version)) != 0) {
if(IsWidget())
opcode = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqCreateWidgetCmd);
else if(IsSign())
opcode = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqCreateSignWidgetCmd);
else
opcode = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqCreateGhostCmd);
}
memcpy(ptr, &opcode, sizeof(opcode));
ptr += sizeof(opcode);
@ -931,7 +934,13 @@ EQ2Packet* Spawn::player_position_update_packet(Player* player, int16 version){
size += 2;
}
static const int8 oversized = 255;
int16 opcode_val = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqUpdateGhostCmd);
int16 opcode_val = 0;
if (EQOpcodeManager.count(GetOpcodeVersion(version)) != 0) {
opcode_val = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqUpdateGhostCmd);
}
uchar* tmp = new uchar[size];
memset(tmp, 0, size);
uchar* ptr = tmp;
@ -991,7 +1000,11 @@ EQ2Packet* Spawn::spawn_update_packet(Player* player, int16 version, bool overri
uchar* pos_changes = 0;
uchar* vis_changes = 0;
static const int8 oversized = 255;
int16 opcode_val = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqUpdateGhostCmd);
int16 opcode_val = 0;
if (EQOpcodeManager.count(GetOpcodeVersion(version)) != 0) {
opcode_val = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqUpdateGhostCmd);
}
//We need to lock these variables up to make this thread safe
m_Update.writelock(__FUNCTION__, __LINE__);

View file

@ -359,16 +359,16 @@ void WorldDatabase::UpdateCharacterMacro(int32 char_id, int8 number, const char*
//we use our timestamp just in case db is on another server, otherwise times might be off
void WorldDatabase::UpdateVitality(int32 timestamp, float amount){
Query query;
Query query, query2, query3;
LogWrite(PLAYER__DEBUG, 3, "Player", "Reset Vitality > 100: %f", amount);
query.RunQuery2(Q_UPDATE, "update character_details set xp_vitality=100 where (xp_vitality + %f) > 100", amount);
LogWrite(PLAYER__DEBUG, 3, "Player", "Update Vitality <= 100: %f", amount);
query.RunQuery2(Q_UPDATE, "update character_details set xp_vitality=(xp_vitality+%f) where (xp_vitality + %f) <= 100", amount, amount);
query2.RunQuery2(Q_UPDATE, "update character_details set xp_vitality=(xp_vitality+%f) where (xp_vitality + %f) <= 100", amount, amount);
LogWrite(PLAYER__DEBUG, 3, "Player", "Update Vitality Timer: %u", timestamp);
query.RunQuery2(Q_UPDATE, "update variables set variable_value=%u where variable_name='vitalitytimer'", timestamp);
query3.RunQuery2(Q_UPDATE, "update variables set variable_value=%u where variable_name='vitalitytimer'", timestamp);
}
void WorldDatabase::SaveVariable(const char* name, const char* value, const char* comment){
@ -2030,14 +2030,14 @@ bool WorldDatabase::UpdateCharacterTimeStamp(int32 account_id, int32 character_i
}
bool WorldDatabase::insertCharacterProperty(Client* client, char* propName, char* propValue) {
Query query;
Query query, query2;
string update_status = string("update character_properties set propvalue='%s' where charid=%i and propname='%s'");
query.RunQuery2(Q_UPDATE, update_status.c_str(), propValue, client->GetCharacterID(), propName);
if (!query.GetAffectedRows())
{
query.RunQuery2(Q_UPDATE, "insert into character_properties (charid, propname, propvalue) values(%i, '%s', '%s')", client->GetCharacterID(), propName, propValue);
if (query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) {
query2.RunQuery2(Q_UPDATE, "insert into character_properties (charid, propname, propvalue) values(%i, '%s', '%s')", client->GetCharacterID(), propName, propValue);
if (query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF) {
LogWrite(WORLD__ERROR, 0, "World", "Error in insertCharacterProperty query '%s': %s", query.GetQuery(), query.GetError());
return false;
}
@ -2722,7 +2722,6 @@ void WorldDatabase::SaveCharRepeatableQuest(Client* client, int32 quest_id, int1
}
void WorldDatabase::SaveCharacterQuestProgress(Client* client, Quest* quest){
Query query;
vector<QuestStep*>* steps = quest->GetQuestSteps();
vector<QuestStep*>::iterator itr;
QuestStep* step = 0;
@ -2730,14 +2729,16 @@ void WorldDatabase::SaveCharacterQuestProgress(Client* client, Quest* quest){
if(steps){
for(itr = steps->begin(); itr != steps->end(); itr++){
step = *itr;
if(step && step->GetQuestCurrentQuantity() > 0)
if(step && step->GetQuestCurrentQuantity() > 0) {
Query query;
query.RunQuery2(Q_REPLACE, "replace into character_quest_progress (char_id, quest_id, step_id, progress) values(%u, %u, %u, %i)", client->GetCharacterID(), quest->GetQuestID(), step->GetStepID(), step->GetQuestCurrentQuantity());
if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF)
LogWrite(WORLD__ERROR, 0, "World", "Error in SaveCharacterQuestProgress query '%s': %s", query.GetQuery(), query.GetError());
}
}
}
quest->MQuestSteps.releasereadlock(__FUNCTION__, __LINE__);
if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF)
LogWrite(WORLD__ERROR, 0, "World", "Error in SaveCharacterQuestProgress query '%s': %s", query.GetQuery(), query.GetError());
}
void WorldDatabase::LoadCharacterQuestProgress(Client* client){
@ -4312,15 +4313,15 @@ void WorldDatabase::LoadFactionAlliances()
bool WorldDatabase::UpdateSpawnScriptData(int32 spawn_id, int32 spawn_location_id, int32 spawnentry_id, const char* name){
bool ret = false;
if((spawn_id > 0 || spawn_location_id > 0 || spawnentry_id > 0) && name){
Query query;
Query query, query2;
int32 row_id = 0;
if(spawn_id > 0){
query.RunQuery2(Q_DELETE, "DELETE FROM spawn_scripts where spawn_id=%u", spawn_id);
query.RunQuery2(Q_INSERT, "INSERT into spawn_scripts (spawn_id, lua_script) values(%u, '%s')", spawn_id, getSafeEscapeString(name).c_str());
if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF)
LogWrite(LUA__ERROR, 0, "LUA", "Error in UpdateSpawnScriptData, Query: %s, Error: %s", query.GetQuery(), query.GetError());
query2.RunQuery2(Q_INSERT, "INSERT into spawn_scripts (spawn_id, lua_script) values(%u, '%s')", spawn_id, getSafeEscapeString(name).c_str());
if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF)
LogWrite(LUA__ERROR, 0, "LUA", "Error in UpdateSpawnScriptData, Query: %s, Error: %s", query2.GetQuery(), query2.GetError());
else{
row_id = query.GetLastInsertedID();
row_id = query2.GetLastInsertedID();
if(row_id > 0)
world.AddSpawnScript(row_id, name);
ret = true;
@ -4328,11 +4329,11 @@ bool WorldDatabase::UpdateSpawnScriptData(int32 spawn_id, int32 spawn_location_i
}
else if(spawn_location_id > 0){
query.RunQuery2(Q_DELETE, "DELETE FROM spawn_scripts where spawn_location_id=%u", spawn_location_id);
query.RunQuery2(Q_INSERT, "INSERT into spawn_scripts (spawn_location_id, lua_script) values(%u, '%s')", spawn_location_id, getSafeEscapeString(name).c_str());
if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF)
LogWrite(LUA__ERROR, 0, "LUA", "Error in UpdateSpawnScriptData, Query: %s, Error: %s", query.GetQuery(), query.GetError());
query2.RunQuery2(Q_INSERT, "INSERT into spawn_scripts (spawn_location_id, lua_script) values(%u, '%s')", spawn_location_id, getSafeEscapeString(name).c_str());
if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF)
LogWrite(LUA__ERROR, 0, "LUA", "Error in UpdateSpawnScriptData, Query: %s, Error: %s", query2.GetQuery(), query2.GetError());
else{
row_id = query.GetLastInsertedID();
row_id = query2.GetLastInsertedID();
if(row_id > 0)
world.AddSpawnLocationScript(row_id, name);
ret = true;
@ -4340,11 +4341,11 @@ bool WorldDatabase::UpdateSpawnScriptData(int32 spawn_id, int32 spawn_location_i
}
else if(spawnentry_id > 0){
query.RunQuery2(Q_DELETE, "DELETE FROM spawn_scripts where spawnentry_id=%u", spawnentry_id);
query.RunQuery2(Q_INSERT, "INSERT into spawn_scripts (spawnentry_id, lua_script) values(%u, '%s')", spawnentry_id, getSafeEscapeString(name).c_str());
if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF)
LogWrite(LUA__ERROR, 0, "LUA", "Error in UpdateSpawnScriptData, Query: %s, Error: %s", query.GetQuery(), query.GetError());
query2.RunQuery2(Q_INSERT, "INSERT into spawn_scripts (spawnentry_id, lua_script) values(%u, '%s')", spawnentry_id, getSafeEscapeString(name).c_str());
if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF)
LogWrite(LUA__ERROR, 0, "LUA", "Error in UpdateSpawnScriptData, Query: %s, Error: %s", query2.GetQuery(), query2.GetError());
else{
row_id = query.GetLastInsertedID();
row_id = query2.GetLastInsertedID();
if(row_id > 0)
world.AddSpawnEntryScript(row_id, name);
ret = true;
@ -5021,16 +5022,15 @@ bool WorldDatabase::DeleteCharacter(int32 account_id, int32 character_id){
if((guild = guild_list.GetGuild(GetGuildIDByCharacterID(character_id))))
guild->RemoveGuildMember(character_id);
Query query;
Query query2;
Query query, query2, query3, query4, query5;
query.RunQuery2(Q_DELETE, "DELETE FROM characters WHERE id=%u AND account_id=%u", character_id, account_id);
//delete languages
query2.RunQuery2(Q_DELETE, "DELETE FROM character_languages WHERE char_id=%u", character_id);
//delete quest rewards
query2.RunQuery2(Q_DELETE, "DELETE FROM character_quest_rewards WHERE char_id=%u", character_id);
query2.RunQuery2(Q_DELETE, "DELETE FROM character_quest_temporary_rewards WHERE char_id=%u", character_id);
query3.RunQuery2(Q_DELETE, "DELETE FROM character_quest_rewards WHERE char_id=%u", character_id);
query4.RunQuery2(Q_DELETE, "DELETE FROM character_quest_temporary_rewards WHERE char_id=%u", character_id);
//delete character claims
query2.RunQuery2(Q_DELETE, "DELETE FROM character_claim_items where char_id=%u", character_id);
query5.RunQuery2(Q_DELETE, "DELETE FROM character_claim_items where char_id=%u", character_id);
if(!query.GetAffectedRows())
{
@ -6377,6 +6377,7 @@ void WorldDatabase::LoadCharacterHistory(int32 char_id, Player *player)
strcpy(hd->Location, result.GetString(4));
// skipped event id as use for it has not been determined yet
hd->EventDate = result.GetInt32(6);
hd->needs_save = false;
player->LoadPlayerHistory(type, subtype, hd);
}
@ -8186,7 +8187,8 @@ void WorldDatabase::UpdateStartingLanguage(int32 char_id, uint8 race_id, int32 s
if (mysql_num_rows(result) > 0) {
while (result && (row = mysql_fetch_row(result))){
//add custom languages to the character_languages db.
query.RunQuery2(Q_INSERT, "INSERT IGNORE INTO character_languages (char_id, language_id) VALUES (%u,%u)",char_id, atoul(row[0]));
Query query2;
query2.RunQuery2(Q_INSERT, "INSERT IGNORE INTO character_languages (char_id, language_id) VALUES (%u,%u)",char_id, atoul(row[0]));
}
}
}
@ -8223,8 +8225,8 @@ void WorldDatabase::LoadClaimItems(int32 char_id)
max_claim = 1;
curr_claim = 1;
}
MYSQL_RES* res = query.RunQuery2(Q_INSERT, "insert ignore into character_claim_items (char_id, item_id, max_claim, curr_claim, one_per_char, veteran_reward_time, account_id) values (%i, %i, %i, %i, %i, %I64i, %i)", char_id, item_id, max_claim, curr_claim, one_per_char, vet_reward_time, acct_id);
Query query2;
MYSQL_RES* res = query2.RunQuery2(Q_INSERT, "insert ignore into character_claim_items (char_id, item_id, max_claim, curr_claim, one_per_char, veteran_reward_time, account_id) values (%i, %i, %i, %i, %i, %I64i, %i)", char_id, item_id, max_claim, curr_claim, one_per_char, vet_reward_time, acct_id);
total++;
}
}
@ -8386,8 +8388,8 @@ void WorldDatabase::ClaimItem(int32 char_id, int32 item_id, Client* client) {
}
}
Query query2;
query2.RunQuery2(Q_UPDATE, "UPDATE `character_claim_items` SET `curr_claim`=0 , `last_claim`=%u WHERE char_id=%i and item_id=%i", curr_time, char_id, item_id);
Query query4;
query4.RunQuery2(Q_UPDATE, "UPDATE `character_claim_items` SET `curr_claim`=0 , `last_claim`=%u WHERE char_id=%i and item_id=%i", curr_time, char_id, item_id);
//give the item to the player, and update last claim time.
//client->AddItem(item_id);
bool item_added = client->AddItem(item_id);

View file

@ -1107,6 +1107,15 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
if (EQOpcodeManager.count(GetOpcodeVersion(version)) == 0) {
LogWrite(WORLD__ERROR, 0, "World", "Incompatible version: %i", version);
ClientPacketFunctions::SendLoginDenied(this);
/* reset version and protect server from trying to send packets out to a bad client
** cause of Dec 6th/Dec 7th 2023 crash
** Client::MakeSpawnChangePacket
** int16 opcode_val = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqUpdateGhostCmd); <-- crashes pulling opcode with bad version
*/
version = 546;
ready_for_updates = false;
ready_for_spawns = false;
return false;
}
@ -2732,6 +2741,12 @@ bool Client::HandleLootItem(Spawn* entity, Item* item) {
lua_interface->RunItemScript(item->GetItemScript(), "obtained", item, player);
CheckPlayerQuestsItemUpdate(item);
if(GetVersion() <= 546) {
EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion());
if (outapp)
QueuePacket(outapp);
}
return true;
}
else
@ -2777,8 +2792,15 @@ void Client::HandleLoot(EQApplicationPacket* app) {
item = new Item(master_item);
if (item) {
loot_all = HandleLootItem(0, item);
if (loot_all)
if (loot_all) {
player->RemovePendingLootItem(loot_id, item->details.item_id);
if(GetVersion() <= 546) {
EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion());
if (outapp)
QueuePacket(outapp);
}
}
}
}
}
@ -2807,9 +2829,11 @@ void Client::HandleLoot(EQApplicationPacket* app) {
safe_delete(packet);
}
}
EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion());
if (outapp)
QueuePacket(outapp);
if(GetVersion() > 546) {
EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion());
if (outapp)
QueuePacket(outapp);
}
Loot(0, player->GetPendingLootItems(loot_id), spawn);
}
else {
@ -2829,9 +2853,11 @@ void Client::HandleLoot(EQApplicationPacket* app) {
safe_delete(packet);
}
}
EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion());
if (outapp)
QueuePacket(outapp);
if(GetVersion() > 546) {
EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion());
if (outapp)
QueuePacket(outapp);
}
Loot(spawn);
if (!spawn->HasLoot()) {
CloseLoot(loot_id);
@ -11056,7 +11082,11 @@ void Client::SendSpawnChanges(set<Spawn*>& spawns) {
void Client::MakeSpawnChangePacket(map<int32, SpawnData> info_changes, map<int32, SpawnData> pos_changes, map<int32, SpawnData> vis_changes, int32 info_size, int32 pos_size, int32 vis_size)
{
static const int8 oversized = 255;
int16 opcode_val = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqUpdateGhostCmd);
int16 opcode_val = 0;
if (EQOpcodeManager.count(GetOpcodeVersion(version)) != 0) {
opcode_val = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(OP_EqUpdateGhostCmd);
}
int32 size = info_size + pos_size + vis_size + 8;
if (version > 283) //version 283 and below uses an overload for size, not always 4 bytes
size += 3;