/* * Ascent MMORPG Server * Copyright (C) 2005-2007 Ascent Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "StdAfx.h" initialiseSingleton(MailSystem); void MailSystem::StartMailSystem() { Log.Notice("MailSystem", "Starting Mail System..."); LoadMessages(); } void MailSystem::ShutdownMailSystem() { sLog.outString("Shutting down mail system..."); sLog.outString(" Deleting all messages / mailboxes..."); MailboxMap::iterator itr; for(itr = Mailboxes.begin(); itr != Mailboxes.end(); ++itr) delete itr->second; Mailboxes.clear(); sLog.outString("Mail system shut down."); } Mailbox* MailSystem::CreateMailbox(uint64 ownerguid) { Mailbox * box = new Mailbox(ownerguid); Mailboxes[ownerguid] = box; return box; } Mailbox* MailSystem::GetPlayersMailbox(uint64 player_guid, bool create) { MUTEX.Acquire(); Mailbox * box; MailboxMap::iterator itr = Mailboxes.find(player_guid); if(itr == Mailboxes.end()) box = create ? CreateMailbox(player_guid) : 0; else box = itr->second; MUTEX.Release(); return box; } uint32 MailSystem::GenerateMessageID() { ++message_high; return message_high; } MailError MailSystem::DeliverMessage(uint64 recipent, MailMessage* message) { Mailbox * box = GetPlayersMailbox(recipent, true); assert(box); if(box->MessageCount() == 50) // Full mailbox return MAIL_ERR_INTERNAL_ERROR; box->AddMessage(message); if((uint32)time(NULL)>=message->delivery_time)// this is still wrong this must be sent on mail recieve //but some1 fucked system.... and delivery is not ever used { // If the player is online, send SMSG_RECEIVED_MAIL to make the letter show up Player * plr = objmgr.GetPlayer(recipent); if(plr && plr->GetSession()) { WorldPacket data(SMSG_RECEIVED_MAIL, 4); data << uint32(0); plr->GetSession()->SendPacket(&data); } } return MAIL_OK; } void Mailbox::AddMessage(MailMessage* Message) { Messages[Message->message_id] = *Message; } void Mailbox::DeleteMessage(uint32 MessageId, bool sql) { Messages.erase(MessageId); if(sql) CharacterDatabase.Execute("DELETE FROM mailbox WHERE message_id = %u", MessageId); } WorldPacket * Mailbox::BuildMailboxListingPacket() { WorldPacket * data = new WorldPacket(SMSG_MAIL_LIST_RESULT, 500); MessageMap::iterator itr; uint32 count = 0; uint32 t = time(NULL); *data << uint8(0); // size placeholder for(itr = Messages.begin(); itr != Messages.end(); ++itr) { if(itr->second.expire_time && t > itr->second.expire_time) continue; // expired mail -> skip it if(itr->second.AddMessageDataToPacket(*data)) ++count; if(count == 50) break; } const_cast(data->contents())[0] = count; // do cleanup on request mail CleanupExpiredMessages(); return data; } void Mailbox::CleanupExpiredMessages() { MessageMap::iterator itr, it2; uint32 curtime = time(NULL); for(itr = Messages.begin(); itr != Messages.end();) { it2 = itr++; if(it2->second.expire_time && it2->second.expire_time < curtime) { Messages.erase(it2); } } } bool MailMessage::AddMessageDataToPacket(WorldPacket& data) { // add stuff if(deleted_flag) return false; data << message_id; data << uint8(message_type); if(message_type) data << uint32(sender_guid); else data << sender_guid; data << subject; data << message_id; // itempageid data << message_id; data << stationary; uint32 itementry = 0, itemcount = 0; uint32 charges = 0, durability = 0, maxdurability = 0; if(attached_item_guid) { stringstream ss; ss << "SELECT `entry`, `count`, `charges`, `durability` FROM playeritems WHERE guid='" << GUID_LOPART(attached_item_guid) << "'"; QueryResult * result = CharacterDatabase.Query(ss.str().c_str()); if(result) { itementry = result->Fetch()[0].GetUInt32(); itemcount = result->Fetch()[1].GetUInt32(); charges = result->Fetch()[2].GetUInt32(); durability = result->Fetch()[3].GetUInt32(); ItemPrototype * it = ItemPrototypeStorage.LookupEntry(itementry); maxdurability = it ? it->MaxDurability : durability; delete result; } } data << itementry; data << uint32(0); // unk data << uint32(0); // unk data << uint32(0); // unk for(uint32 i = 0; i < 17; ++i) data << uint32(0); data << (itemcount ? uint8(itemcount) : uint8(1)); data << charges; data << maxdurability; data << durability; data << money; data << cod; data << uint32(read_flag); if(expire_time) data << float(float(expire_time - time(NULL)) / 86400.0f); else data << float(0); data << uint32(0); return true; } void MailSystem::SaveMessageToSQL(MailMessage * message) { stringstream ss; ss << "INSERT INTO mailbox VALUES(" << message->message_id << "," << message->message_type << "," << message->player_guid << "," << message->sender_guid << ",\"" << CharacterDatabase.EscapeString(message->subject) << "\",\"" << CharacterDatabase.EscapeString(message->body) << "\"," << message->money << ",'" << message->attached_item_guid << "'," << message->cod << "," << message->stationary << "," << message->expire_time << "," << message->delivery_time << "," << message->copy_made << "," << message->read_flag << "," << message->deleted_flag << ")"; CharacterDatabase.Execute(ss.str().c_str()); } void WorldSession::HandleSendMail(WorldPacket & recv_data ) { MailMessage msg; uint64 gameobject; uint32 unk2; string recepient; //uint32 err = MAIL_OK; recv_data >> gameobject >> recepient; recv_data >> msg.subject >> msg.body >> msg.stationary; recv_data >> unk2 >> msg.attached_item_guid >> msg.money >> msg.cod; // Search for the recipient PlayerInfo * player = ObjectMgr::getSingleton().GetPlayerInfoByName(recepient); if(player == 0) { SendMailError(MAIL_ERR_RECIPIENT_NOT_FOUND); return; } bool interfaction = false; if(sMailSystem.MailOption(MAIL_FLAG_CAN_SEND_TO_OPPOSITE_FACTION) || (HasGMPermissions() && sMailSystem.MailOption(MAIL_FLAG_CAN_SEND_TO_OPPOSITE_FACTION_GM))) { interfaction = true; } // Check we're sending to the same faction (disable this for testing) if(player->team != _player->GetTeam() && !interfaction) { SendMailError(MAIL_ERR_NOT_YOUR_ALLIANCE); return; } // Check if we're sending mail to ourselves if(player->name == _player->GetName()) { SendMailError(MAIL_ERR_CANNOT_SEND_TO_SELF); return; } // Instant delivery time by default. msg.delivery_time = time(NULL); // Set up the cost int32 cost = 0; if(!sMailSystem.MailOption(MAIL_FLAG_DISABLE_POSTAGE_COSTS) && !(GetPermissionCount() && sMailSystem.MailOption(MAIL_FLAG_NO_COST_FOR_GM))) { cost = 30; } // Check for attached money if(msg.money > 0) cost += msg.money; // check that we have enough in our backpack if((int32)_player->GetUInt32Value(PLAYER_FIELD_COINAGE) < cost) { SendMailError(MAIL_ERR_NOT_ENOUGH_MONEY); return; } // Check for the item, and required item. if(msg.attached_item_guid != 0) { Item * attached_item = _player->GetItemInterface()->SafeRemoveAndRetreiveItemByGuid(msg.attached_item_guid, false); if(attached_item == 0 || attached_item->IsSoulbound()) { // could not find item SendMailError(MAIL_ERR_INTERNAL_ERROR); return; } // Save the item with no owner (to objectmgr) attached_item->RemoveFromWorld(); attached_item->SetOwner(NULL); attached_item->SaveToDB(INVENTORY_SLOT_NOT_SET, 0,true); // Cut out the high part of the attached item. msg.attached_item_guid = attached_item->GetGUID(); if(GetPermissionCount() > 0) { /* log the message */ sGMLog.writefromsession(this, "sent mail with item entry %u to %s, with gold %u.", attached_item->GetEntry(), player->name.c_str(), msg.money); } // Delete from memory now, its not needed anymore. delete attached_item; // no need to update guid, it shouldn't change.. } if(msg.money != 0 || msg.cod != 0 || msg.attached_item_guid != 0 && player->acct != _player->GetSession()->GetAccountId()) { if(!sMailSystem.MailOption(MAIL_FLAG_DISABLE_HOUR_DELAY_FOR_ITEMS)) msg.delivery_time += 3600; // 1hr } // take the money _player->ModUInt32Value(PLAYER_FIELD_COINAGE, -cost); // Fill in the rest of the info msg.player_guid = player->guid; msg.sender_guid = _player->GetGUID(); // 30 day expiry time for unread mail mail if(!sMailSystem.MailOption(MAIL_FLAG_NO_EXPIRY)) msg.expire_time = time(NULL) + (TIME_DAY * 30); else msg.expire_time = 0; msg.copy_made = false; msg.read_flag = false; msg.deleted_flag = false; msg.message_type = 0; // Make an ID for us msg.message_id = sMailSystem.GenerateMessageID(); // Save the message to database sMailSystem.SaveMessageToSQL(&msg); // Great, all our info is filled in. Now we can add it to the other players mailbox. sMailSystem.DeliverMessage(player->guid, &msg); // Success packet :) SendMailError(MAIL_OK); } void WorldSession::HandleMarkAsRead(WorldPacket & recv_data ) { uint64 mailbox; uint32 message_id; recv_data >> mailbox >> message_id; Mailbox * m = sMailSystem.GetPlayersMailbox(_player->GetGUID(), false); if(m == 0) return; MailMessage * message = m->GetMessage(message_id); if(message == 0) return; // mark the message as read message->read_flag = 1; // mail now has a 3 day expiry time if(!sMailSystem.MailOption(MAIL_FLAG_NO_EXPIRY)) message->expire_time = time(NULL) + (TIME_DAY * 3); // update it in sql CharacterDatabase.Execute("UPDATE mailbox SET read_flag = 1, expiry_time = %u WHERE message_id = %u", message->message_id, message->expire_time); } void WorldSession::HandleMailDelete(WorldPacket & recv_data ) { uint64 mailbox; uint32 message_id; recv_data >> mailbox >> message_id; WorldPacket data(SMSG_SEND_MAIL_RESULT, 12); data << message_id << uint32(MAIL_RES_DELETED); Mailbox * m = sMailSystem.GetPlayersMailbox(_player->GetGUID(), false); if(m == 0) { data << uint32(MAIL_ERR_INTERNAL_ERROR); SendPacket(&data); return; } MailMessage * message = m->GetMessage(message_id); if(message == 0) { data << uint32(MAIL_ERR_INTERNAL_ERROR); SendPacket(&data); return; } if(message->copy_made) { // we have the message as a copy on the item. we can't delete it or this item // will no longer function. // deleted_flag prevents it from being shown in the mail list. message->deleted_flag = 1; // update in sql CharacterDatabase.Execute("UPDATE mailbox SET deleted_flag = 1 WHERE message_id = %u", message_id); } else { // delete the message, there are no other references to it. m->DeleteMessage(message_id, true); } data << uint32(MAIL_OK); SendPacket(&data); } void WorldSession::HandleTakeItem(WorldPacket & recv_data ) { uint64 mailbox; uint32 message_id; recv_data >> mailbox >> message_id; WorldPacket data(SMSG_SEND_MAIL_RESULT, 12); data << message_id << uint32(MAIL_RES_ITEM_TAKEN); Mailbox * m = sMailSystem.GetPlayersMailbox(_player->GetGUID(), false); if(m == 0) { data << uint32(MAIL_ERR_INTERNAL_ERROR); SendPacket(&data); return; } MailMessage * message = m->GetMessage(message_id); if(message == 0 || !message->attached_item_guid) { data << uint32(MAIL_ERR_INTERNAL_ERROR); SendPacket(&data); return; } // check for cod credit if(message->cod > 0) { if(_player->GetUInt32Value(PLAYER_FIELD_COINAGE) < message->cod) { data << uint32(MAIL_ERR_NOT_ENOUGH_MONEY); SendPacket(&data); return; } } // grab the item Item * item = 0; // try cached auctionhouse items first, // this will stop crashes later on :p /*if(message->message_type == AUCTION) item = objmgr.GetAItem(message->attached_item_guid);*/ if(item == 0) item = objmgr.LoadItem(message->attached_item_guid); if(item == 0) { // doesn't exist data << uint32(MAIL_ERR_INTERNAL_ERROR); SendPacket(&data); return; } // find a free bag slot SlotResult result = _player->GetItemInterface()->FindFreeInventorySlot(item->GetProto()); if(result.Result == 0) { // no free slots left! data << uint32(MAIL_ERR_BAG_FULL); SendPacket(&data); delete item; return; } // all is good // delete the item (so when its resaved it'll have an association) item->DeleteFromDB(); // add the item to their backpack _player->GetItemInterface()->AddItemToFreeSlot(item); // message no longer has an item association message->attached_item_guid = 0; // update in sql! CharacterDatabase.Execute("UPDATE mailbox SET attached_item_guid = 0, cod = 0 WHERE message_id = %u", message->message_id); // send complete packet data << uint32(MAIL_OK); SendPacket(&data); if( message->cod > 0 ) { _player->ModUInt32Value(PLAYER_FIELD_COINAGE, -int32(message->cod)); string subject = "COD Payment: "; subject += message->subject; sMailSystem.SendAutomatedMessage(NORMAL, message->player_guid, message->sender_guid, subject, "", message->cod, 0, 0, 1); } // prolly need to send an item push here } void WorldSession::HandleTakeMoney(WorldPacket & recv_data ) { uint64 mailbox; uint32 message_id; recv_data >> mailbox >> message_id; WorldPacket data(SMSG_SEND_MAIL_RESULT, 12); data << message_id << uint32(MAIL_RES_MONEY_TAKEN); Mailbox * m = sMailSystem.GetPlayersMailbox(_player->GetGUID(), false); if(m == 0) { data << uint32(MAIL_ERR_INTERNAL_ERROR); SendPacket(&data); return; } MailMessage * message = m->GetMessage(message_id); if(message == 0 || !message->money) { data << uint32(MAIL_ERR_INTERNAL_ERROR); SendPacket(&data); return; } // add the money to the player _player->ModUInt32Value(PLAYER_FIELD_COINAGE, message->money); // message no longer has any money message->money = 0; // update in sql! CharacterDatabase.Execute("UPDATE mailbox SET money = 0 WHERE message_id = %u", message->message_id); // send result data << uint32(MAIL_OK); SendPacket(&data); } void WorldSession::HandleReturnToSender(WorldPacket & recv_data ) { uint64 mailbox; uint32 message_id; recv_data >> mailbox >> message_id; WorldPacket data(SMSG_SEND_MAIL_RESULT, 12); data << message_id << uint32(MAIL_RES_RETURNED_TO_SENDER); Mailbox * m = sMailSystem.GetPlayersMailbox(_player->GetGUID(), false); if(m == 0) { data << uint32(MAIL_ERR_INTERNAL_ERROR); SendPacket(&data); return; } MailMessage * msg = m->GetMessage(message_id); if(msg == 0) { data << uint32(MAIL_ERR_INTERNAL_ERROR); SendPacket(&data); return; } // copy into a new struct MailMessage message = *msg; // remove the old message m->DeleteMessage(message_id, true); // re-assign the owner/sender message.player_guid = message.sender_guid; message.sender_guid = _player->GetGUID(); // turn off the read flag message.read_flag = false; message.deleted_flag = false; message.copy_made = false; // assign a new id message.message_id = sMailSystem.GenerateMessageID(); // null out the cod charges. (the sender doesnt want to have to pay for his own item // that he got nothing for.. :p) message.cod = 0; // assign new delivery time message.delivery_time = message.attached_item_guid ? time(NULL) + 3600 : time(NULL); // add to the senders mailbox sMailSystem.DeliverMessage(message.player_guid, &message); // add to sql sMailSystem.SaveMessageToSQL(&message); // finish the packet data << uint32(MAIL_OK); SendPacket(&data); } void WorldSession::HandleMailCreateTextItem(WorldPacket & recv_data ) { uint64 mailbox; uint32 message_id; recv_data >> mailbox >> message_id; WorldPacket data(SMSG_SEND_MAIL_RESULT, 12); data << message_id << uint32(MAIL_RES_MADE_PERMANENT); Mailbox * m = sMailSystem.GetPlayersMailbox(_player->GetGUID(), false); if(m == 0) { data << uint32(MAIL_ERR_INTERNAL_ERROR); SendPacket(&data); return; } ItemPrototype * proto = ItemPrototypeStorage.LookupEntry(8383); MailMessage * message = m->GetMessage(message_id); if(message == 0 || !proto) { data << uint32(MAIL_ERR_INTERNAL_ERROR); SendPacket(&data); return; } SlotResult result = _player->GetItemInterface()->FindFreeInventorySlot(proto); if(result.Result == 0) { data << uint32(MAIL_ERR_INTERNAL_ERROR); SendPacket(&data); return; } Item * pItem = objmgr.CreateItem(8383, _player); pItem->SetUInt32Value(ITEM_FIELD_ITEM_TEXT_ID, message_id); _player->GetItemInterface()->AddItemToFreeSlot(pItem); // mail now has an item after it message->copy_made = true; // update in sql CharacterDatabase.Execute("UPDATE mailbox SET copy_made = 1 WHERE message_id = %u", message_id); data << uint32(MAIL_OK); SendPacket(&data); } void WorldSession::HandleItemTextQuery(WorldPacket & recv_data) { uint32 message_id; recv_data >> message_id; string body = "Internal Error"; Mailbox * m = sMailSystem.GetPlayersMailbox(_player->GetGUID(), false); if(m != 0) { MailMessage * message = m->GetMessage(message_id); if(message != 0) body = message->body; } WorldPacket data(SMSG_ITEM_TEXT_QUERY_RESPONSE, body.length() + 5); data << message_id << body; SendPacket(&data); } bool Mailbox::HasUnreadMessages() { MessageMap::iterator iter = Messages.begin(); for(; iter != Messages.end(); ++iter) { if(iter->second.read_flag == 0) return true; } return false; } void WorldSession::HandleMailTime(WorldPacket & recv_data) { WorldPacket data(MSG_QUERY_NEXT_MAIL_TIME, 4); Mailbox * m = sMailSystem.GetPlayersMailbox(_player->GetGUID(), false); if(m && m->HasUnreadMessages()) data << uint32(0); else data << uint32(0xc7a8c000); SendPacket(&data); } void WorldSession::SendMailError(uint32 error) { WorldPacket data(SMSG_SEND_MAIL_RESULT, 12); data << uint32(0); data << uint32(MAIL_RES_MAIL_SENT); data << error; SendPacket(&data); } void WorldSession::HandleGetMail(WorldPacket & recv_data ) { Mailbox * box = sMailSystem.GetPlayersMailbox(_player->GetGUID(), false); if(box == 0) { OutPacket(SMSG_MAIL_LIST_RESULT, 1, "\x00"); return; } WorldPacket * data = box->BuildMailboxListingPacket(); SendPacket(data); delete data; } void MailSystem::RemoveMessageIfDeleted(uint32 message_id, uint64 player_guid) { Mailbox * box = GetPlayersMailbox(player_guid, false); if(box == 0) return; MailMessage * msg = box->GetMessage(message_id); if(msg == 0) return; if(msg->deleted_flag) // we've deleted from inbox box->DeleteMessage(message_id, true); // wipe the message } void MailSystem::SendAutomatedMessage(uint32 type, uint64 sender, uint64 receiver, string subject, string body, uint32 money, uint32 cod, uint64 item_guid, uint32 stationary) { // This is for sending automated messages, for example from an auction house. MailMessage msg; msg.message_type = type; msg.sender_guid = sender; msg.player_guid = receiver; msg.subject = subject; msg.body = body; msg.money = money; msg.cod = cod; msg.attached_item_guid = item_guid; msg.stationary = stationary; msg.delivery_time = time(NULL); msg.expire_time = 0; msg.read_flag = false; msg.copy_made = false; msg.message_id = GenerateMessageID(); msg.deleted_flag = false; // Send the message. DeliverMessage(receiver, &msg); // Save it in sql (important:P) SaveMessageToSQL(&msg); } void MailSystem::PeriodicMailRefresh() { // this funtion will reload the mail from the database. use it if you've set up some external // source of getting mail (e.g. a web-based interface) MUTEX.Acquire(); // clear all mailboxes MailboxMap::iterator iter = Mailboxes.begin(); for(; iter != Mailboxes.end(); ++iter) delete iter->second; Mailboxes.clear(); // create/load from the database LoadMessages(); MUTEX.Release(); } void MailSystem::LoadMessages() { Log.Notice("MailSystem", "Creating/Loading Mailboxes..."); uint32 high = 0; QueryResult * result = CharacterDatabase.Query("SELECT * FROM mailbox"); if(result) { Field * fields; uint32 message_id; MailMessage msg; Mailbox * box; uint64 owner; uint32 i; do { fields = result->Fetch(); owner = fields[2].GetUInt64(); box = GetPlayersMailbox(owner, true); assert(box); // check message id message_id = fields[0].GetUInt32(); if(message_id > high) high = message_id; // Create message struct i = 0; msg.message_id = fields[i++].GetUInt32(); msg.message_type = fields[i++].GetUInt32(); msg.player_guid = fields[i++].GetUInt32(); msg.sender_guid = fields[i++].GetUInt32(); msg.subject = fields[i++].GetString(); msg.body = fields[i++].GetString(); msg.money = fields[i++].GetUInt32(); msg.attached_item_guid = fields[i++].GetUInt64(); msg.cod = fields[i++].GetUInt32(); msg.stationary = fields[i++].GetUInt32(); msg.expire_time = fields[i++].GetUInt32(); msg.delivery_time = fields[i++].GetUInt32(); msg.copy_made = fields[i++].GetBool(); msg.read_flag = fields[i++].GetBool(); msg.deleted_flag = fields[i++].GetBool(); // Add to the mailbox box->AddMessage(&msg); } while(result->NextRow()); delete result; } message_high = high; Log.Notice("MailSystem", "Deleting expired messages..."); MailboxMap::iterator iter; for(iter = Mailboxes.begin(); iter != Mailboxes.end(); ++iter) { iter->second->CleanupExpiredMessages(); } Log.Notice("MailSystem", "Deleting expired mailboxes..."); Mailbox * dbox; for(iter = Mailboxes.begin(); iter != Mailboxes.end();) { dbox = iter->second; ++iter; if(dbox->MessageCount() == 0) { Mailboxes.erase(dbox->GetOwner()); delete dbox; } } }