From c0faed2251dff3814fe60e1a8ba86a046e48f707 Mon Sep 17 00:00:00 2001 From: azazel Date: Sun, 17 Oct 2010 19:54:13 +0600 Subject: Core/Guilds: guild code was completely refactored and rewritten. * OOP desing and implementation; * all the queries are moved to prepared statements; * guild loading is optimized; * all the possible interaction with guild's data is done inside the guild class; * added more hooks to GuildScript class; WARNING: Make sure you backup your characters database before applying this change (just in case). Known problems with guilds: * when new member is added to the guild, MOTD is not displayed for him in guild tab of social window; * if you add item with random property to guild bank visual representation of item below it becomes wrong (it displays wrong stack number); * packets order differs from official: currently guild bank packet traffic is twice as more than on offy. --HG-- branch : trunk --- src/server/game/Guilds/Guild.cpp | 3810 ++++++++++++++++++++++---------------- 1 file changed, 2188 insertions(+), 1622 deletions(-) (limited to 'src/server/game/Guilds/Guild.cpp') diff --git a/src/server/game/Guilds/Guild.cpp b/src/server/game/Guilds/Guild.cpp index 3ed1a6a9d7a..b0a4b1f4bc5 100755 --- a/src/server/game/Guilds/Guild.cpp +++ b/src/server/game/Guilds/Guild.cpp @@ -17,2173 +17,2739 @@ */ #include "DatabaseEnv.h" -#include "WorldPacket.h" -#include "WorldSession.h" -#include "Player.h" -#include "Opcodes.h" -#include "ObjectMgr.h" #include "Guild.h" +#include "ScriptMgr.h" #include "Chat.h" -#include "SocialMgr.h" -#include "Util.h" -#include "Language.h" -#include "World.h" #include "Config.h" -#include "ScriptMgr.h" - -Guild::Guild() -{ - m_Id = 0; - m_Name = ""; - m_LeaderGuid = 0; - GINFO = MOTD = ""; - m_EmblemStyle = 0; - m_EmblemColor = 0; - m_BorderStyle = 0; - m_BorderColor = 0; - m_BackgroundColor = 0; - m_accountsNumber = 0; - - m_CreatedDate = time(0); - - m_GuildBankMoney = 0; +#include "SocialMgr.h" +#include "Log.h" - m_GuildEventLogNextGuid = 0; - m_GuildBankEventLogNextGuid_Money = 0; +#define MAX_GUILD_BANK_TAB_TEXT_LEN 500 +#define EMBLEM_PRICE 10 * GOLD - for (uint8 i = 0; i < GUILD_BANK_MAX_TABS; ++i) - m_GuildBankEventLogNextGuid_Item[i] = 0; +inline void _CharacterExecutePreparedStatement(SQLTransaction& trans, PreparedStatement* stmt) +{ + if (trans.null()) + CharacterDatabase.Execute(stmt); + else + trans->Append(stmt); } -Guild::~Guild() +inline uint32 _GetGuildBankTabPrice(uint8 tabId) { - SQLTransaction temp = SQLTransaction(NULL); - DeleteGuildBankItems(temp); + switch (tabId) + { + case 0: return 100; + case 1: return 250; + case 2: return 500; + case 3: return 1000; + case 4: return 2500; + case 5: return 5000; + default: return 0; + } } -bool Guild::Create(Player* leader, std::string gname) +void Guild::SendCommandResult(WorldSession* session, GuildCommandType type, GuildCommandError errCode, const std::string& param) { - if (sObjectMgr.GetGuildByName(gname)) - return false; - - WorldSession* lSession = leader->GetSession(); - if (!lSession) - return false; - - m_LeaderGuid = leader->GetGUID(); - m_Name = gname; - GINFO = ""; - MOTD = "No message set."; - m_GuildBankMoney = 0; - m_Id = sObjectMgr.GenerateGuildId(); - - sLog.outDebug("GUILD: creating guild %s to leader: %u", gname.c_str(), GUID_LOPART(m_LeaderGuid)); - - // gname already assigned to Guild::name, use it to encode string for DB - CharacterDatabase.escape_string(gname); - - std::string dbGINFO = GINFO; - std::string dbMOTD = MOTD; - CharacterDatabase.escape_string(dbGINFO); - CharacterDatabase.escape_string(dbMOTD); + WorldPacket data(SMSG_GUILD_COMMAND_RESULT, 8 + param.size() + 1); + data << uint32(type); + data << param; + data << uint32(errCode); + session->SendPacket(&data); - SQLTransaction trans = CharacterDatabase.BeginTransaction(); - // CharacterDatabase.PExecute("DELETE FROM guild WHERE guildid='%u'", Id); - MAX(guildid)+1 not exist - trans->PAppend("DELETE FROM guild_member WHERE guildid='%u'", m_Id); - trans->PAppend("INSERT INTO guild (guildid,name,leaderguid,info,motd,createdate,EmblemStyle,EmblemColor,BorderStyle,BorderColor,BackgroundColor,BankMoney) " - "VALUES('%u','%s','%u', '%s', '%s', UNIX_TIMESTAMP(NOW()),'%u','%u','%u','%u','%u','" UI64FMTD "')", - m_Id, gname.c_str(), GUID_LOPART(m_LeaderGuid), dbGINFO.c_str(), dbMOTD.c_str(), m_EmblemStyle, m_EmblemColor, m_BorderStyle, m_BorderColor, m_BackgroundColor, m_GuildBankMoney); - CharacterDatabase.CommitTransaction(trans); + sLog.outDebug("WORLD: Sent (SMSG_GUILD_COMMAND_RESULT)"); +} - CreateDefaultGuildRanks(lSession->GetSessionDbLocaleIndex()); +void Guild::SendSaveEmblemResult(WorldSession* session, GuildEmblemError errCode) +{ + WorldPacket data(MSG_SAVE_GUILD_EMBLEM, 4); + data << uint32(errCode); + session->SendPacket(&data); - return AddMember(m_LeaderGuid, (uint32)GR_GUILDMASTER); + sLog.outDebug("WORLD: Sent (MSG_SAVE_GUILD_EMBLEM)"); } -void Guild::CreateDefaultGuildRanks(LocaleConstant locale_idx) +/////////////////////////////////////////////////////////////////////////////// +// LogHolder +Guild::LogHolder::~LogHolder() { - CharacterDatabase.PExecute("DELETE FROM guild_rank WHERE guildid='%u'", m_Id); - CharacterDatabase.PExecute("DELETE FROM guild_bank_right WHERE guildid = '%u'", m_Id); + // Cleanup + for (GuildLog::iterator itr = m_log.begin(); itr != m_log.end(); ++itr) + delete (*itr); +} - CreateRank(sObjectMgr.GetTrinityString(LANG_GUILD_MASTER, locale_idx), GR_RIGHT_ALL); - CreateRank(sObjectMgr.GetTrinityString(LANG_GUILD_OFFICER, locale_idx), GR_RIGHT_ALL); - CreateRank(sObjectMgr.GetTrinityString(LANG_GUILD_VETERAN, locale_idx), GR_RIGHT_GCHATLISTEN | GR_RIGHT_GCHATSPEAK); - CreateRank(sObjectMgr.GetTrinityString(LANG_GUILD_MEMBER, locale_idx), GR_RIGHT_GCHATLISTEN | GR_RIGHT_GCHATSPEAK); - CreateRank(sObjectMgr.GetTrinityString(LANG_GUILD_INITIATE, locale_idx), GR_RIGHT_GCHATLISTEN | GR_RIGHT_GCHATSPEAK); +// Adds event loaded from database to collection +inline void Guild::LogHolder::LoadEvent(LogEntry* entry) +{ + if (m_nextGUID == GUILD_EVENT_LOG_GUID_UNDEFINED) + m_nextGUID = entry->GetGUID(); + m_log.push_front(entry); } -bool Guild::AddMember(uint64 plGuid, uint32 plRank) +// Adds new event happened in game. +// If maximum number of events is reached, oldest event is removed from collection. +inline void Guild::LogHolder::AddEvent(SQLTransaction& trans, LogEntry* entry) { - Player* pl = sObjectMgr.GetPlayer(plGuid); - if (pl) - { - if (pl->GetGuildId() != 0) - return false; - } - else + // Check max records limit + if (m_log.size() >= m_maxRecords) { - if (Player::GetGuildIdFromDB(plGuid) != 0) // player already in guild - return false; + LogEntry* oldEntry = m_log.front(); + delete oldEntry; + m_log.pop_front(); } + // Add event to list + m_log.push_back(entry); + // Save to DB + entry->SaveToDB(trans); +} - sScriptMgr.OnGuildAddMember(this, pl, plRank); - - // remove all player signs from another petitions - // this will be prevent attempt joining player to many guilds and corrupt guild data integrity - Player::RemovePetitionsAndSigns(plGuid, 9); - - // fill player data - MemberSlot newmember; +// Writes information about all events into packet. +inline void Guild::LogHolder::WritePacket(WorldPacket& data) const +{ + data << uint8(m_log.size()); + for (GuildLog::const_iterator itr = m_log.begin(); itr != m_log.end(); ++itr) + (*itr)->WritePacket(data); +} - if (pl) - { - newmember.accountId = pl->GetSession()->GetAccountId(); - newmember.Name = pl->GetName(); - newmember.ZoneId = pl->GetZoneId(); - newmember.Level = pl->getLevel(); - newmember.Class = pl->getClass(); - } +inline uint32 Guild::LogHolder::GetNextGUID() +{ + // Next guid was not initialized. It means there are no records for this holder in DB yet. + // Start from the beginning. + if (m_nextGUID == GUILD_EVENT_LOG_GUID_UNDEFINED) + m_nextGUID = 0; else + m_nextGUID = (m_nextGUID + 1) % m_maxRecords; + return m_nextGUID; +} + +/////////////////////////////////////////////////////////////////////////////// +// EventLogEntry +void Guild::EventLogEntry::SaveToDB(SQLTransaction& trans) const +{ + PreparedStatement* stmt = NULL; + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_EVENTLOG); + stmt->setUInt32(0, m_guildId); + stmt->setUInt32(1, m_guid); + _CharacterExecutePreparedStatement(trans, stmt); + + uint8 index = 0; + stmt = CharacterDatabase.GetPreparedStatement(CHAR_ADD_GUILD_EVENTLOG); + stmt->setUInt32( index, m_guildId); + stmt->setUInt32(++index, m_guid); + stmt->setUInt8 (++index, uint8(m_eventType)); + stmt->setUInt32(++index, m_playerGuid1); + stmt->setUInt32(++index, m_playerGuid2); + stmt->setUInt8 (++index, m_newRank); + stmt->setUInt64(++index, m_timestamp); + _CharacterExecutePreparedStatement(trans, stmt); +} + +void Guild::EventLogEntry::WritePacket(WorldPacket& data) const +{ + // Event type + data << uint8(m_eventType); + // Player 1 + data << uint64(MAKE_NEW_GUID(m_playerGuid1, 0, HIGHGUID_PLAYER)); + // Player 2 not for left/join guild events + if (m_eventType != GUILD_EVENT_LOG_JOIN_GUILD && m_eventType != GUILD_EVENT_LOG_LEAVE_GUILD) + data << uint64(MAKE_NEW_GUID(m_playerGuid2, 0, HIGHGUID_PLAYER)); + // New Rank - only for promote/demote guild events + if (m_eventType == GUILD_EVENT_LOG_PROMOTE_PLAYER || m_eventType == GUILD_EVENT_LOG_DEMOTE_PLAYER) + data << uint8(m_newRank); + // Event timestamp + data << uint32(::time(NULL) - m_timestamp); +} + +/////////////////////////////////////////////////////////////////////////////// +// BankEventLogEntry +void Guild::BankEventLogEntry::SaveToDB(SQLTransaction& trans) const +{ + PreparedStatement* stmt = NULL; + uint8 index = 0; + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_BANK_EVENTLOG); + stmt->setUInt32( index, m_guildId); + stmt->setUInt32(++index, m_guid); + stmt->setUInt8 (++index, m_bankTabId); + _CharacterExecutePreparedStatement(trans, stmt); + + index = 0; + stmt = CharacterDatabase.GetPreparedStatement(CHAR_ADD_GUILD_BANK_EVENTLOG); + stmt->setUInt32( index, m_guildId); + stmt->setUInt32(++index, m_guid); + stmt->setUInt8 (++index, m_bankTabId); + stmt->setUInt8 (++index, uint8(m_eventType)); + stmt->setUInt32(++index, m_playerGuid); + stmt->setUInt32(++index, m_itemOrMoney); + stmt->setUInt16(++index, m_itemStackCount); + stmt->setUInt8 (++index, m_destTabId); + stmt->setUInt64(++index, m_timestamp); + _CharacterExecutePreparedStatement(trans, stmt); +} + +void Guild::BankEventLogEntry::WritePacket(WorldPacket& data) const +{ + data << uint8(m_eventType); + data << uint64(MAKE_NEW_GUID(m_playerGuid, 0, HIGHGUID_PLAYER)); + data << uint32(m_itemOrMoney); + if (!IsMoneyEvent(m_eventType)) { - QueryResult result = CharacterDatabase.PQuery("SELECT name,zone,level,class,account FROM characters WHERE guid = '%u'", GUID_LOPART(plGuid)); - if (!result) - return false; // player doesn't exist - - Field *fields = result->Fetch(); - newmember.Name = fields[0].GetString(); - newmember.ZoneId = fields[1].GetUInt32(); - newmember.Level = fields[2].GetUInt8(); - newmember.Class = fields[3].GetUInt8(); - newmember.accountId = fields[4].GetInt32(); - - if (newmember.Level < 1 || //newmember.Level > STRONG_MAX_LEVEL || - newmember.Class < CLASS_WARRIOR || newmember.Class >= MAX_CLASSES) - { - sLog.outError("Player (GUID: %u) has a broken data in field `characters` table, cannot add him to guild.",GUID_LOPART(plGuid)); - return false; - } + data << uint32(m_itemStackCount); + if (m_eventType == GUILD_BANK_LOG_MOVE_ITEM || m_eventType == GUILD_BANK_LOG_MOVE_ITEM2) + data << uint8(m_destTabId); } + data << uint32(::time(NULL) - m_timestamp); +} - newmember.RankId = plRank; - newmember.OFFnote = (std::string)""; - newmember.Pnote = (std::string)""; - newmember.LogoutTime = time(NULL); - newmember.BankResetTimeMoney = 0; // this will force update at first query - for (uint8 i = 0; i < GUILD_BANK_MAX_TABS; ++i) - newmember.BankResetTimeTab[i] = 0; - members[GUID_LOPART(plGuid)] = newmember; - - std::string dbPnote = newmember.Pnote; - std::string dbOFFnote = newmember.OFFnote; - CharacterDatabase.escape_string(dbPnote); - CharacterDatabase.escape_string(dbOFFnote); +/////////////////////////////////////////////////////////////////////////////// +// RankInfo +bool Guild::RankInfo::LoadFromDB(Field* fields) +{ + m_rankId = fields[1].GetUInt8(); + m_name = fields[2].GetString(); + m_rights = fields[3].GetUInt32(); + m_bankMoneyPerDay = fields[4].GetUInt32(); + if (m_rankId == GR_GUILDMASTER) // Prevent loss of leader rights + m_rights |= GR_RIGHT_ALL; + return true; +} - CharacterDatabase.PExecute("INSERT INTO guild_member (guildid,guid,rank,pnote,offnote) VALUES ('%u', '%u', '%u','%s','%s')", - m_Id, GUID_LOPART(plGuid), newmember.RankId, dbPnote.c_str(), dbOFFnote.c_str()); +void Guild::RankInfo::SaveToDB(SQLTransaction& trans) const +{ + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_ADD_GUILD_RANK); + stmt->setUInt32(0, m_guildId); + stmt->setUInt8 (1, m_rankId); + stmt->setString(2, m_name); + stmt->setUInt32(3, m_rights); + _CharacterExecutePreparedStatement(trans, stmt); +} - // If player not in game data in data field will be loaded from guild tables, no need to update it!! - if (pl) +void Guild::RankInfo::WritePacket(WorldPacket& data) const +{ + data << uint32(m_rights); + data << uint32(m_bankMoneyPerDay); // In game set in gold, in packet set in bronze. + for (uint8 i = 0; i < GUILD_BANK_MAX_TABS; ++i) { - pl->SetInGuild(m_Id); - pl->SetRank(newmember.RankId); - pl->SetGuildIdInvited(0); + data << uint32(m_bankTabRightsAndSlots[i].rights); + data << uint32(m_bankTabRightsAndSlots[i].slots); } - - UpdateAccountsNumber(); - - return true; } -void Guild::SetMOTD(std::string motd) +void Guild::RankInfo::SetName(const std::string& name) { - MOTD = motd; + if (m_name == name) + return; - sScriptMgr.OnGuildMOTDChanged(this, motd); + m_name = name; - // motd now can be used for encoding to DB - CharacterDatabase.escape_string(motd); - CharacterDatabase.PExecute("UPDATE guild SET motd='%s' WHERE guildid='%u'", motd.c_str(), m_Id); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SET_GUILD_RANK_NAME); + stmt->setString(0, m_name); + stmt->setUInt8 (1, m_rankId); + stmt->setUInt32(2, m_guildId); + CharacterDatabase.Execute(stmt); } -void Guild::SetGINFO(std::string ginfo) +void Guild::RankInfo::SetRights(uint32 rights) { - GINFO = ginfo; + if (m_rankId == GR_GUILDMASTER) // Prevent loss of leader rights + rights = GR_RIGHT_ALL; + + if (m_rights == rights) + return; - sScriptMgr.OnGuildInfoChanged(this, ginfo); + m_rights = rights; - // ginfo now can be used for encoding to DB - CharacterDatabase.escape_string(ginfo); - CharacterDatabase.PExecute("UPDATE guild SET info='%s' WHERE guildid='%u'", ginfo.c_str(), m_Id); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SET_GUILD_RANK_RIGHTS); + stmt->setUInt32(0, m_rights); + stmt->setUInt8 (1, m_rankId); + stmt->setUInt32(2, m_guildId); + CharacterDatabase.Execute(stmt); } -bool Guild::LoadGuildFromDB(QueryResult guildDataResult) +void Guild::RankInfo::SetBankMoneyPerDay(uint32 money) { - if (!guildDataResult) - return false; + if (m_rankId == GR_GUILDMASTER) // Prevent loss of leader rights + money = GUILD_WITHDRAW_MONEY_UNLIMITED; - Field *fields = guildDataResult->Fetch(); - - m_Id = fields[0].GetUInt32(); - m_Name = fields[1].GetString(); - m_LeaderGuid = MAKE_NEW_GUID(fields[2].GetUInt32(), 0, HIGHGUID_PLAYER); - m_EmblemStyle = fields[3].GetUInt32(); - m_EmblemColor = fields[4].GetUInt32(); - m_BorderStyle = fields[5].GetUInt32(); - m_BorderColor = fields[6].GetUInt32(); - m_BackgroundColor = fields[7].GetUInt32(); - GINFO = fields[8].GetString(); - MOTD = fields[9].GetString(); - m_CreatedDate = fields[10].GetUInt64(); - m_GuildBankMoney = fields[11].GetUInt64(); - - uint32 purchasedTabs = fields[12].GetUInt32(); - if (purchasedTabs > GUILD_BANK_MAX_TABS) - purchasedTabs = GUILD_BANK_MAX_TABS; + if (m_bankMoneyPerDay == money) + return; - m_TabListMap.resize(purchasedTabs); - for (uint8 i = 0; i < purchasedTabs; ++i) - m_TabListMap[i] = new GuildBankTab(); + m_bankMoneyPerDay = money; - return true; + PreparedStatement* stmt = NULL; + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SET_GUILD_RANK_BANK_MONEY); + stmt->setUInt32(0, money); + stmt->setUInt8 (1, m_rankId); + stmt->setUInt32(2, m_guildId); + CharacterDatabase.Execute(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_RESET_GUILD_RANK_BANK_RESET_TIME); + stmt->setUInt32(0, m_guildId); + stmt->setUInt8 (1, m_rankId); + CharacterDatabase.Execute(stmt); } -bool Guild::CheckGuildStructure() +void Guild::RankInfo::SetBankTabSlotsAndRights(uint8 tabId, GuildBankRightsAndSlots rightsAndSlots, bool saveToDB) { - // Repair the structure of guild - // If the guildmaster doesn't exist or isn't the member of guild - // attempt to promote another member - int32 GM_rights = GetRank(GUID_LOPART(m_LeaderGuid)); - if (GM_rights == -1) + if (m_rankId == GR_GUILDMASTER) // Prevent loss of leader rights + rightsAndSlots.SetGuildMasterValues(); + + if (m_bankTabRightsAndSlots[tabId].IsEqual(rightsAndSlots)) + return; + + m_bankTabRightsAndSlots[tabId] = rightsAndSlots; + + if (saveToDB) { - DelMember(m_LeaderGuid); - // check no members case (disbanded) - if (members.empty()) - return false; + PreparedStatement* stmt = NULL; + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_BANK_RIGHT); + stmt->setUInt32(0, m_guildId); + stmt->setUInt8 (1, tabId); + stmt->setUInt8 (2, m_rankId); + CharacterDatabase.Execute(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_ADD_GUILD_BANK_RIGHT); + stmt->setUInt32(0, m_guildId); + stmt->setUInt8 (1, tabId); + stmt->setUInt8 (2, m_rankId); + stmt->setUInt8 (3, m_bankTabRightsAndSlots[tabId].rights); + stmt->setUInt32(4, m_bankTabRightsAndSlots[tabId].slots); + CharacterDatabase.Execute(stmt); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_RESET_GUILD_RANK_BANK_TIME0 + tabId); + stmt->setUInt32(0, m_guildId); + stmt->setUInt8 (1, m_rankId); + CharacterDatabase.Execute(stmt); } - else if (GM_rights != GR_GUILDMASTER) - SetLeader(m_LeaderGuid); - - // Check config if multiple guildmasters are allowed - if (sConfig.GetBoolDefault("Guild.AllowMultipleGuildMaster", 0) == 0) - for (MemberList::const_iterator itr = members.begin(); itr != members.end(); ++itr) - if (itr->second.RankId == GR_GUILDMASTER && GUID_LOPART(m_LeaderGuid) != itr->first) // Allow only 1 guildmaster - ChangeRank(itr->first, GR_OFFICER); // set right of member to officer +} +/////////////////////////////////////////////////////////////////////////////// +// BankTab +bool Guild::BankTab::LoadFromDB(Field* fields) +{ + m_name = fields[2].GetString(); + m_icon = fields[3].GetString(); + m_text = fields[4].GetString(); return true; } -bool Guild::LoadRanksFromDB(QueryResult guildRanksResult) +bool Guild::BankTab::LoadItemFromDB(Field* fields) { - if (!guildRanksResult) + uint8 slotId = fields[13].GetUInt8(); + uint32 itemGuid = fields[14].GetUInt32(); + uint32 itemEntry = fields[15].GetUInt32(); + if (slotId >= GUILD_BANK_MAX_SLOTS) { - sLog.outError("Guild %u has broken `guild_rank` data, creating new...",m_Id); - CreateDefaultGuildRanks(DEFAULT_LOCALE); - return true; + sLog.outError("Invalid slot for item (GUID: %u, id: %u) in guild bank, skipped.", itemGuid, itemEntry); + return false; } - Field *fields; - bool broken_ranks = false; - - //GUILD RANKS are sequence starting from 0 = GUILD_MASTER (ALL PRIVILEGES) to max 9 (lowest privileges) - //the lower rank id is considered higher rank - so promotion does rank-- and demotion does rank++ - //between ranks in sequence cannot be gaps - so 0,1,2,4 cannot be - //min ranks count is 5 and max is 10. - - do + ItemPrototype const* proto = sObjectMgr.GetItemPrototype(itemEntry); + if (!proto) { - fields = guildRanksResult->Fetch(); - //condition that would be true when all ranks in QueryResult will be processed and guild without ranks is being processed - if (!fields) - break; - - uint32 guildId = fields[0].GetUInt32(); - if (guildId < m_Id) - { - //there is in table guild_rank record which doesn't have guildid in guild table, report error - sLog.outErrorDb("Guild %u does not exist but it has a record in guild_rank table, deleting it!", guildId); - CharacterDatabase.PExecute("DELETE FROM guild_rank WHERE guildid = '%u'", guildId); - continue; - } - - if (guildId > m_Id) - //we loaded all ranks for this guild already, break cycle - break; - uint32 rankID = fields[1].GetUInt32(); - std::string rankName = fields[2].GetString(); - uint32 rankRights = fields[3].GetUInt32(); - uint32 rankMoney = fields[4].GetUInt32(); - - if (rankID != m_Ranks.size()) // guild_rank.ids are sequence 0,1,2,3.. - broken_ranks = true; + sLog.outError("Unknown item (GUID: %u, id: %u) in guild bank, skipped.", itemGuid, itemEntry); + return false; + } - //first rank is guildmaster, prevent loss leader rights - if (m_Ranks.empty()) - rankRights |= GR_RIGHT_ALL; + Item *pItem = NewItemOrBag(proto); + if (!pItem->LoadFromDB(itemGuid, 0, fields, itemEntry)) + { + sLog.outError("Item (GUID %u, id: %u) not found in item_instance, deleting from guild bank!", itemGuid, itemEntry); - AddRank(rankName,rankRights,rankMoney); - }while (guildRanksResult->NextRow()); + PreparedStatement *stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_NONEXISTENT_GUILD_BANK_ITEM); + stmt->setUInt32(0, m_guildId); + stmt->setUInt8 (1, m_tabId); + stmt->setUInt8 (2, slotId); + CharacterDatabase.Execute(stmt); - if (m_Ranks.size() < GUILD_RANKS_MIN_COUNT) // if too few ranks, renew them - { - m_Ranks.clear(); - sLog.outError("Guild %u has broken `guild_rank` data, creating new...", m_Id); - CreateDefaultGuildRanks(DEFAULT_LOCALE); - broken_ranks = false; - } - // guild_rank have wrong numbered ranks, repair - if (broken_ranks) - { - sLog.outError("Guild %u has broken `guild_rank` data, repairing...", m_Id); - SQLTransaction trans = CharacterDatabase.BeginTransaction(); - trans->PAppend("DELETE FROM guild_rank WHERE guildid='%u'", m_Id); - for (size_t i = 0; i < m_Ranks.size(); ++i) - { - std::string name = m_Ranks[i].Name; - uint32 rights = m_Ranks[i].Rights; - CharacterDatabase.escape_string(name); - trans->PAppend("INSERT INTO guild_rank (guildid,rid,rname,rights) VALUES ('%u', '%u', '%s', '%u')", m_Id, uint32(i), name.c_str(), rights); - } - CharacterDatabase.CommitTransaction(trans); + delete pItem; + return false; } + pItem->AddToWorld(); + m_items[slotId] = pItem; return true; } -bool Guild::LoadMembersFromDB(QueryResult guildMembersResult) +// Deletes contents of the tab from the world (and from DB if necessary) +void Guild::BankTab::Delete(SQLTransaction& trans, bool removeItemsFromDB) { - if (!guildMembersResult) - return false; - - UpdateAccountsNumber(); - - do - { - Field *fields = guildMembersResult->Fetch(); - //this condition will be true when all rows in QueryResult are processed and new guild without members is going to be loaded - prevent crash - if (!fields) - break; - - uint32 guildId = fields[0].GetUInt32(); - if (guildId < m_Id) + for (uint8 slotId = 0; slotId < GUILD_BANK_MAX_SLOTS; ++slotId) + if (Item* pItem = m_items[slotId]) { - //there is in table guild_member record which doesn't have guildid in guild table, report error - sLog.outErrorDb("Guild %u does not exist but it has a record in guild_member table, deleting it!", guildId); - CharacterDatabase.PExecute("DELETE FROM guild_member WHERE guildid = '%u'", guildId); - continue; + pItem->RemoveFromWorld(); + if (removeItemsFromDB) + pItem->DeleteFromDB(trans); + delete pItem; + pItem = NULL; } +} - if (guildId > m_Id) - //we loaded all members for this guild already, break cycle - break; - - MemberSlot newmember; - uint64 guid = MAKE_NEW_GUID(fields[1].GetUInt32(), 0, HIGHGUID_PLAYER); - newmember.RankId = fields[2].GetUInt32(); - //don't allow member to have not existing rank! - if (newmember.RankId >= m_Ranks.size()) - newmember.RankId = GetLowestRank(); - - newmember.Pnote = fields[3].GetString(); - newmember.OFFnote = fields[4].GetString(); - newmember.BankResetTimeMoney = fields[5].GetUInt32(); - newmember.BankRemMoney = fields[6].GetUInt32(); - for (uint8 i = 0; i < GUILD_BANK_MAX_TABS; ++i) - { - newmember.BankResetTimeTab[i] = fields[7+(2*i)].GetUInt32(); - newmember.BankRemSlotsTab[i] = fields[8+(2*i)].GetUInt32(); - } +inline void Guild::BankTab::WritePacket(WorldPacket& data) const +{ + data << uint8(GUILD_BANK_MAX_SLOTS); + for (uint8 slotId = 0; slotId < GUILD_BANK_MAX_SLOTS; ++slotId) + WriteSlotPacket(data, slotId); +} - newmember.Name = fields[19].GetString(); - newmember.Level = fields[20].GetUInt8(); - newmember.Class = fields[21].GetUInt8(); - newmember.ZoneId = fields[22].GetUInt32(); - newmember.LogoutTime = fields[23].GetUInt64(); - newmember.accountId = fields[24].GetInt32(); +// Writes information about contents of specified slot into packet. +void Guild::BankTab::WriteSlotPacket(WorldPacket& data, uint8 slotId) const +{ + Item *pItem = GetItem(slotId); + uint32 itemEntry = pItem ? pItem->GetEntry() : 0; - //this code will remove unexisting character guids from guild - if (newmember.Level < 1 /*|| newmember.Level > STRONG_MAX_LEVEL*/) // can be at broken `data` field - { - sLog.outError("Player (GUID: %u) has a broken data in field `characters`.`data`, deleting him from guild!",GUID_LOPART(guid)); - CharacterDatabase.PExecute("DELETE FROM guild_member WHERE guid = '%u'", GUID_LOPART(guid)); - continue; - } - if (!newmember.ZoneId) - { - sLog.outError("Player (GUID: %u) has broken zone-data", GUID_LOPART(guid)); - // here it will also try the same, to get the zone from characters-table, but additional it tries to find - // the zone through xy coords .. this is a bit redundant, but shouldn't be called often - newmember.ZoneId = Player::GetZoneIdFromDB(guid); - } - if (newmember.Class < CLASS_WARRIOR || newmember.Class >= MAX_CLASSES) // can be at broken `class` field - { - sLog.outError("Player (GUID: %u) has a broken data in field `characters`.`class`, deleting him from guild!",GUID_LOPART(guid)); - CharacterDatabase.PExecute("DELETE FROM guild_member WHERE guid = '%u'", GUID_LOPART(guid)); - continue; - } + data << uint8(slotId); + data << uint32(itemEntry); + if (itemEntry) + { + data << uint32(0); // 3.3.0 (0x00018020, 0x00018000) + data << uint32(pItem->GetItemRandomPropertyId()); // Random item property id - members[GUID_LOPART(guid)] = newmember; + if (pItem->GetItemRandomPropertyId()) + data << uint32(pItem->GetItemSuffixFactor()); // SuffixFactor - }while (guildMembersResult->NextRow()); + data << uint32(pItem->GetCount()); // ITEM_FIELD_STACK_COUNT + data << uint32(0); + data << uint8(abs(pItem->GetSpellCharges())); // Spell charges - if (members.empty()) - return false; + uint8 enchCount = 0; + size_t enchCountPos = data.wpos(); - return true; + data << uint8(enchCount); // Number of enchantments + for (uint32 i = PERM_ENCHANTMENT_SLOT; i < MAX_ENCHANTMENT_SLOT; ++i) + if (uint32 enchId = pItem->GetEnchantmentId(EnchantmentSlot(i))) + { + data << uint8(i); + data << uint32(enchId); + ++enchCount; + } + data.put(enchCountPos, enchCount); + } } -void Guild::SetMemberStats(uint64 guid) +void Guild::BankTab::SetInfo(const std::string& name, const std::string& icon) { - MemberList::iterator itr = members.find(GUID_LOPART(guid)); - if (itr == members.end()) - return; - - Player *pl = ObjectAccessor::FindPlayer(guid); - if (!pl) + if (m_name == name && m_icon == icon) return; - itr->second.Name = pl->GetName(); - itr->second.Level = pl->getLevel(); - itr->second.Class = pl->getClass(); - itr->second.ZoneId = pl->GetZoneId(); -} -void Guild::SetLeader(uint64 guid) -{ - m_LeaderGuid = guid; - ChangeRank(guid, GR_GUILDMASTER); + m_name = name; + m_icon = icon; - CharacterDatabase.PExecute("UPDATE guild SET leaderguid='%u' WHERE guildid='%u'", GUID_LOPART(guid), m_Id); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SET_GUILD_BANK_TAB_INFO); + stmt->setString(0, m_name); + stmt->setString(1, m_icon); + stmt->setUInt32(2, m_guildId); + stmt->setUInt8 (3, m_tabId); + CharacterDatabase.Execute(stmt); } -void Guild::DelMember(uint64 guid, bool isDisbanding, bool isKicked) +void Guild::BankTab::SetText(const std::string& text) { - Player *player = sObjectMgr.GetPlayer(guid); - sScriptMgr.OnGuildRemoveMember(this, player, isDisbanding, isKicked); - - //guild master can be deleted when loading guild and guid doesn't exist in characters table - //or when he is removed from guild by gm command - if (m_LeaderGuid == guid && !isDisbanding) - { - MemberSlot* oldLeader = NULL; - MemberSlot* best = NULL; - uint64 newLeaderGUID = 0; - for (Guild::MemberList::iterator i = members.begin(); i != members.end(); ++i) - { - if (i->first == GUID_LOPART(guid)) - { - oldLeader = &(i->second); - continue; - } + if (m_text == text) + return; - if (!best || best->RankId > i->second.RankId) - { - best = &(i->second); - newLeaderGUID = i->first; - } - } - if (!best) - { - Disband(); - return; - } + m_text = text; + utf8truncate(m_text, MAX_GUILD_BANK_TAB_TEXT_LEN); // DB and client size limitation - SetLeader(newLeaderGUID); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SET_GUILD_BANK_TAB_TEXT); + stmt->setString(0, m_text); + stmt->setUInt32(1, m_guildId); + stmt->setUInt8 (2, m_tabId); + CharacterDatabase.Execute(stmt); +} - // If player not online data in data field will be loaded from guild tabs no need to update it !! - if (Player *newLeader = sObjectMgr.GetPlayer(newLeaderGUID)) - newLeader->SetRank(GR_GUILDMASTER); +// Sets/removes contents of specified slot. +// If pItem == NULL contents are removed. +bool Guild::BankTab::SetItem(SQLTransaction& trans, uint8 slotId, Item* pItem) +{ + if (slotId >= GUILD_BANK_MAX_SLOTS) + return false; - // when leader non-exist (at guild load with deleted leader only) not send broadcasts - if (oldLeader) - { - BroadcastEvent(GE_LEADER_CHANGED, 0, 2, oldLeader->Name, best->Name, ""); + m_items[slotId] = pItem; - BroadcastEvent(GE_LEFT, guid, 1, oldLeader->Name, "", ""); - } - } + PreparedStatement* stmt = NULL; - members.erase(GUID_LOPART(guid)); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_BANK_ITEM); + stmt->setUInt32(0, m_guildId); + stmt->setUInt8 (1, m_tabId); + stmt->setUInt8 (2, slotId); + _CharacterExecutePreparedStatement(trans, stmt); - // If player not online data in data field will be loaded from guild tabs no need to update it !! - if (player) + if (pItem) { - player->SetInGuild(0); - player->SetRank(0); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_ADD_GUILD_BANK_ITEM); + stmt->setUInt32(0, m_guildId); + stmt->setUInt8 (1, m_tabId); + stmt->setUInt8 (2, slotId); + stmt->setUInt32(3, pItem->GetGUIDLow()); + stmt->setUInt32(4, pItem->GetEntry()); + _CharacterExecutePreparedStatement(trans, stmt); + + pItem->SetUInt64Value(ITEM_FIELD_CONTAINED, 0); + pItem->SetUInt64Value(ITEM_FIELD_OWNER, 0); + pItem->FSetState(ITEM_NEW); + pItem->SaveToDB(trans); // Not in inventory and can be saved standalone } + return true; +} - CharacterDatabase.PExecute("DELETE FROM guild_member WHERE guid = '%u'", GUID_LOPART(guid)); +void Guild::BankTab::SendText(const Guild* pGuild, WorldSession* session) const +{ + WorldPacket data(MSG_QUERY_GUILD_BANK_TEXT, 1 + m_text.size() + 1); + data << uint8(m_tabId); + data << m_text; - if (!isDisbanding) - UpdateAccountsNumber(); + if (session) + session->SendPacket(&data); + else + pGuild->BroadcastPacket(&data); } -void Guild::ChangeRank(uint64 guid, uint32 newRank) +/////////////////////////////////////////////////////////////////////////////// +// Member +void Guild::Member::SetStats(Player* player) { - MemberList::iterator itr = members.find(GUID_LOPART(guid)); - if (itr != members.end()) - itr->second.RankId = newRank; - - Player *player = sObjectMgr.GetPlayer(guid); - // If player not online data in data field will be loaded from guild tabs no need to update it !! - if (player) - player->SetRank(newRank); + m_name = player->GetName(); + m_level = player->getLevel(); + m_class = player->getClass(); + m_zoneId = player->GetZoneId(); + m_accountId = player->GetSession()->GetAccountId(); +} - CharacterDatabase.PExecute("UPDATE guild_member SET rank='%u' WHERE guid='%u'", newRank, GUID_LOPART(guid)); +void Guild::Member::SetStats(const std::string& name, uint8 level, uint8 _class, uint32 zoneId, uint32 accountId) +{ + m_name = name; + m_level = level; + m_class = _class; + m_zoneId = zoneId; + m_accountId = accountId; } -void Guild::SetPNOTE(uint64 guid,std::string pnote) +void Guild::Member::SetPublicNote(const std::string& publicNote) { - MemberList::iterator itr = members.find(GUID_LOPART(guid)); - if (itr == members.end()) + if (m_publicNote == publicNote) return; - itr->second.Pnote = pnote; + m_publicNote = publicNote; - // pnote now can be used for encoding to DB - CharacterDatabase.escape_string(pnote); - CharacterDatabase.PExecute("UPDATE guild_member SET pnote = '%s' WHERE guid = '%u'", pnote.c_str(), itr->first); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SET_GUILD_MEMBER_PNOTE); + stmt->setString(0, publicNote); + stmt->setUInt32(1, GUID_LOPART(m_guid)); + CharacterDatabase.Execute(stmt); } -void Guild::SetOFFNOTE(uint64 guid,std::string offnote) +void Guild::Member::SetOfficerNote(const std::string& officerNote) { - MemberList::iterator itr = members.find(GUID_LOPART(guid)); - if (itr == members.end()) + if (m_officerNote == officerNote) return; - itr->second.OFFnote = offnote; - // offnote now can be used for encoding to DB - CharacterDatabase.escape_string(offnote); - CharacterDatabase.PExecute("UPDATE guild_member SET offnote = '%s' WHERE guid = '%u'", offnote.c_str(), itr->first); + + m_officerNote = officerNote; + + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SET_GUILD_MEMBER_OFFNOTE); + stmt->setString(0, officerNote); + stmt->setUInt32(1, GUID_LOPART(m_guid)); + CharacterDatabase.Execute(stmt); } -void Guild::BroadcastToGuild(WorldSession *session, const std::string& msg, uint32 language) +void Guild::Member::ChangeRank(uint8 newRank) { - if (session && session->GetPlayer() && HasRankRight(session->GetPlayer()->GetRank(),GR_RIGHT_GCHATSPEAK)) - { - WorldPacket data; - ChatHandler(session).FillMessageData(&data, CHAT_MSG_GUILD, language, 0, msg.c_str()); + m_rankId = newRank; - for (MemberList::const_iterator itr = members.begin(); itr != members.end(); ++itr) - { - Player *pl = ObjectAccessor::FindPlayer(MAKE_NEW_GUID(itr->first, 0, HIGHGUID_PLAYER)); + // Update rank information in player's field, if he is online. + if (Player *player = FindPlayer()) + player->SetRank(newRank); - if (pl && pl->GetSession() && HasRankRight(pl->GetRank(),GR_RIGHT_GCHATLISTEN) && !pl->GetSocial()->HasIgnore(session->GetPlayer()->GetGUIDLow())) - pl->GetSession()->SendPacket(&data); - } - } + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SET_GUILD_MEMBER_RANK); + stmt->setUInt8 (0, newRank); + stmt->setUInt32(1, GUID_LOPART(m_guid)); + CharacterDatabase.Execute(stmt); } -void Guild::BroadcastToOfficers(WorldSession *session, const std::string& msg, uint32 language) +void Guild::Member::SaveToDB(SQLTransaction& trans) const { - if (session && session->GetPlayer() && HasRankRight(session->GetPlayer()->GetRank(), GR_RIGHT_OFFCHATSPEAK)) + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_ADD_GUILD_MEMBER); + stmt->setUInt32(0, m_guildId); + stmt->setUInt32(1, GUID_LOPART(m_guid)); + stmt->setUInt8 (2, m_rankId); + stmt->setString(3, m_publicNote); + stmt->setString(4, m_officerNote); + _CharacterExecutePreparedStatement(trans, stmt); +} + +// Loads member's data from database. +// If member has broken fields (level, class) returns false. +// In this case member has to be removed from guild. +bool Guild::Member::LoadFromDB(Field* fields) +{ + m_publicNote = fields[3].GetString(); + m_officerNote = fields[4].GetString(); + m_bankRemaining[GUILD_BANK_MAX_TABS].resetTime = fields[5].GetUInt32(); + m_bankRemaining[GUILD_BANK_MAX_TABS].value = fields[6].GetUInt32(); + for (uint8 i = 0; i < GUILD_BANK_MAX_TABS; ++i) { - for (MemberList::const_iterator itr = members.begin(); itr != members.end(); ++itr) - { - WorldPacket data; - ChatHandler::FillMessageData(&data, session, CHAT_MSG_OFFICER, language, NULL, 0, msg.c_str(), NULL); + m_bankRemaining[i].resetTime = fields[7 + i * 2].GetUInt32(); + m_bankRemaining[i].value = fields[8 + i * 2].GetUInt32(); + } - Player *pl = ObjectAccessor::FindPlayer(MAKE_NEW_GUID(itr->first, 0, HIGHGUID_PLAYER)); + SetStats(fields[19].GetString(), + fields[20].GetUInt8(), + fields[21].GetUInt8(), + fields[22].GetUInt32(), + fields[23].GetUInt32()); + m_logoutTime = fields[24].GetUInt64(); - if (pl && pl->GetSession() && HasRankRight(pl->GetRank(),GR_RIGHT_OFFCHATLISTEN) && !pl->GetSocial()->HasIgnore(session->GetPlayer()->GetGUIDLow())) - pl->GetSession()->SendPacket(&data); - } + if (!CheckStats()) + return false; + + if (!m_zoneId) + { + sLog.outError("Player (GUID: %u) has broken zone-data", GUID_LOPART(m_guid)); + m_zoneId = Player::GetZoneIdFromDB(m_guid); } + return true; } -void Guild::BroadcastPacket(WorldPacket *packet) +// Validate player fields. Returns false if corrupted fields are found. +bool Guild::Member::CheckStats() const { - for (MemberList::const_iterator itr = members.begin(); itr != members.end(); ++itr) + if (m_level < 1) { - Player *player = ObjectAccessor::FindPlayer(MAKE_NEW_GUID(itr->first, 0, HIGHGUID_PLAYER)); - if (player) - player->GetSession()->SendPacket(packet); + sLog.outError("Player (GUID: %u) has a broken data in field `characters`.`level`, deleting him from guild!", GUID_LOPART(m_guid)); + return false; + } + if (m_class < CLASS_WARRIOR || m_class >= MAX_CLASSES) + { + sLog.outError("Player (GUID: %u) has a broken data in field `characters`.`class`, deleting him from guild!", GUID_LOPART(m_guid)); + return false; } + return true; } -void Guild::BroadcastPacketToRank(WorldPacket *packet, uint32 rankId) +void Guild::Member::WritePacket(WorldPacket& data) const { - for (MemberList::const_iterator itr = members.begin(); itr != members.end(); ++itr) + if (Player* player = FindPlayer()) { - if (itr->second.RankId == rankId) - { - Player *player = ObjectAccessor::FindPlayer(MAKE_NEW_GUID(itr->first, 0, HIGHGUID_PLAYER)); - if (player) - player->GetSession()->SendPacket(packet); - } + data << uint64(player->GetGUID()); + data << uint8(1); + data << player->GetName(); + data << uint32(m_rankId); + data << uint8(player->getLevel()); + data << uint8(player->getClass()); + data << uint8(0); // new 2.4.0 + data << uint32(player->GetZoneId()); } + else + { + data << m_guid; + data << uint8(0); + data << m_name; + data << uint32(m_rankId); + data << uint8(m_level); + data << uint8(m_class); + data << uint8(0); // new 2.4.0 + data << uint32(m_zoneId); + data << float(float(::time(NULL) - m_logoutTime) / DAY); + } + data << m_publicNote; + data << m_officerNote; } -void Guild::CreateRank(std::string name_,uint32 rights) +// Decreases amount of money/slots left for today. +// If (tabId == GUILD_BANK_MAX_TABS) decrease money amount. +// Otherwise decrease remaining items amount for specified tab. +void Guild::Member::DecreaseBankRemainingValue(SQLTransaction& trans, uint8 tabId, uint32 amount) { - if (m_Ranks.size() >= GUILD_RANKS_MAX_COUNT) - return; + m_bankRemaining[tabId].value -= amount; - // ranks are sequence 0,1,2,... where 0 means guildmaster - uint32 new_rank_id = m_Ranks.size(); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement( + tabId == GUILD_BANK_MAX_TABS ? + CHAR_SET_GUILD_MEMBER_BANK_REM_MONEY : + CHAR_SET_GUILD_MEMBER_BANK_REM_SLOTS0 + tabId); + stmt->setUInt32(0, m_bankRemaining[tabId].value); + stmt->setUInt32(1, m_guildId); + stmt->setUInt32(2, GUID_LOPART(m_guid)); + _CharacterExecutePreparedStatement(trans, stmt); +} - AddRank(name_, rights, 0); +// Get amount of money/slots left for today. +// If (tabId == GUILD_BANK_MAX_TABS) return money amount. +// Otherwise return remaining items amount for specified tab. +// If reset time was more than 24 hours ago, renew reset time and reset amount to maximum value. +uint32 Guild::Member::GetBankRemainingValue(uint8 tabId, const Guild* pGuild) const +{ + // Guild master has unlimited amount. + if (IsRank(GR_GUILDMASTER)) + return tabId == GUILD_BANK_MAX_TABS ? GUILD_WITHDRAW_MONEY_UNLIMITED : GUILD_WITHDRAW_SLOT_UNLIMITED; - //existing records in db should be deleted before calling this procedure and m_PurchasedTabs must be loaded already + // Check rights for non-money tab. + if (tabId != GUILD_BANK_MAX_TABS) + if ((pGuild->_GetRankBankTabRights(m_rankId, tabId) & GUILD_BANK_RIGHT_VIEW_TAB) != GUILD_BANK_RIGHT_VIEW_TAB) + return 0; - for (uint32 i = 0; i < uint32(GetPurchasedTabs()); ++i) + uint32 curTime = uint32(::time(NULL) / MINUTE); // minutes + if (curTime > m_bankRemaining[tabId].resetTime + 24 * HOUR / MINUTE) { - //create bank rights with 0 - CharacterDatabase.PExecute("INSERT INTO guild_bank_right (guildid,TabId,rid) VALUES ('%u','%u','%u')", m_Id, i, new_rank_id); + RemainingValue& rv = const_cast (m_bankRemaining[tabId]); + rv.resetTime = curTime; + rv.value = tabId == GUILD_BANK_MAX_TABS ? + pGuild->_GetRankBankMoneyPerDay(m_rankId) : + pGuild->_GetRankBankTabSlotsPerDay(m_rankId, tabId); + + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement( + tabId == GUILD_BANK_MAX_TABS ? + CHAR_SET_GUILD_MEMBER_BANK_REM_MONEY : + CHAR_SET_GUILD_MEMBER_BANK_TIME_REM_SLOTS0 + tabId); + stmt->setUInt32(0, m_bankRemaining[tabId].resetTime); + stmt->setUInt32(1, m_bankRemaining[tabId].value); + stmt->setUInt32(2, m_guildId); + stmt->setUInt32(3, GUID_LOPART(m_guid)); + CharacterDatabase.Execute(stmt); } - // name now can be used for encoding to DB - CharacterDatabase.escape_string(name_); - CharacterDatabase.PExecute("INSERT INTO guild_rank (guildid,rid,rname,rights) VALUES ('%u', '%u', '%s', '%u')", m_Id, new_rank_id, name_.c_str(), rights); + return m_bankRemaining[tabId].value; } -void Guild::AddRank(const std::string& name_,uint32 rights, uint32 money) +inline void Guild::Member::ResetTabTimes() { - m_Ranks.push_back(RankInfo(name_,rights,money)); + for (uint8 tabId = 0; tabId < GUILD_BANK_MAX_TABS; ++tabId) + m_bankRemaining[tabId].resetTime = 0; } -void Guild::DelRank() +inline void Guild::Member::ResetMoneyTime() { - // client won't allow to have less than GUILD_RANKS_MIN_COUNT ranks in guild - if (m_Ranks.size() <= GUILD_RANKS_MIN_COUNT) - return; - - // delete lowest guild_rank - uint32 rank = GetLowestRank(); - CharacterDatabase.PExecute("DELETE FROM guild_rank WHERE rid >= '%u' AND guildid='%u'", rank, m_Id); - - m_Ranks.pop_back(); + m_bankRemaining[GUILD_BANK_MAX_TABS].resetTime = 0; } -std::string Guild::GetRankName(uint32 rankId) +/////////////////////////////////////////////////////////////////////////////// +// EmblemInfo +void EmblemInfo::LoadFromDB(Field* fields) { - if (rankId >= m_Ranks.size()) - return ""; - - return m_Ranks[rankId].Name; + m_style = fields[3].GetUInt32(); + m_color = fields[4].GetUInt32(); + m_borderStyle = fields[5].GetUInt32(); + m_borderColor = fields[6].GetUInt32(); + m_backgroundColor = fields[7].GetUInt32(); } -uint32 Guild::GetRankRights(uint32 rankId) +void EmblemInfo::WritePacket(WorldPacket& data) const { - if (rankId >= m_Ranks.size()) - return 0; - - return m_Ranks[rankId].Rights; + data << uint32(m_style); + data << uint32(m_color); + data << uint32(m_borderStyle); + data << uint32(m_borderColor); + data << uint32(m_backgroundColor); } -void Guild::SetRankName(uint32 rankId, std::string name_) +void EmblemInfo::SaveToDB(uint32 guildId) const { - if (rankId >= m_Ranks.size()) - return; - - m_Ranks[rankId].Name = name_; - - // name now can be used for encoding to DB - CharacterDatabase.escape_string(name_); - CharacterDatabase.PExecute("UPDATE guild_rank SET rname='%s' WHERE rid='%u' AND guildid='%u'", name_.c_str(), rankId, m_Id); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SET_GUILD_EMBLEM_INFO); + stmt->setUInt32(0, m_style); + stmt->setUInt32(1, m_color); + stmt->setUInt32(2, m_borderStyle); + stmt->setUInt32(3, m_borderColor); + stmt->setUInt32(4, m_backgroundColor); + stmt->setUInt32(5, guildId); + CharacterDatabase.Execute(stmt); } -void Guild::SetRankRights(uint32 rankId, uint32 rights) +/////////////////////////////////////////////////////////////////////////////// +// MoveItemData +bool Guild::MoveItemData::CheckItem(uint32& splitedAmount) { - if (rankId >= m_Ranks.size()) - return; - - m_Ranks[rankId].Rights = rights; - - CharacterDatabase.PExecute("UPDATE guild_rank SET rights='%u' WHERE rid='%u' AND guildid='%u'", rights, rankId, m_Id); + ASSERT(m_pItem); + if (splitedAmount > m_pItem->GetCount()) + return false; + if (splitedAmount == m_pItem->GetCount()) + splitedAmount = 0; + return true; } -int32 Guild::GetRank(uint32 LowGuid) +uint8 Guild::MoveItemData::CanStore(Item* pItem, bool swap, bool sendError) { - MemberList::const_iterator itr = members.find(LowGuid); - if (itr == members.end()) - return -1; - - return itr->second.RankId; + m_vec.clear(); + uint8 msg = _CanStore(pItem, swap); + if (sendError && msg != EQUIP_ERR_OK) + m_pPlayer->SendEquipError(msg, pItem); + return (msg == EQUIP_ERR_OK); } -void Guild::Disband() +bool Guild::MoveItemData::CloneItem(uint32 count) { - sScriptMgr.OnGuildDisband(this); - BroadcastEvent(GE_DISBANDED, 0, 0, "", "", ""); - - while (!members.empty()) + ASSERT(m_pItem); + m_pClonedItem = m_pItem->CloneItem(count); + if (!m_pClonedItem) { - MemberList::const_iterator itr = members.begin(); - DelMember(MAKE_NEW_GUID(itr->first, 0, HIGHGUID_PLAYER), true); + m_pPlayer->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, m_pItem); + return false; } + return true; +} - SQLTransaction trans = CharacterDatabase.BeginTransaction(); - trans->PAppend("DELETE FROM guild WHERE guildid = '%u'", m_Id); - trans->PAppend("DELETE FROM guild_rank WHERE guildid = '%u'", m_Id); - trans->PAppend("DELETE FROM guild_bank_tab WHERE guildid = '%u'", m_Id); - - //Free bank tab used memory and delete items stored in them - DeleteGuildBankItems(trans, true); +void Guild::MoveItemData::LogAction(MoveItemData* pFrom) const +{ + ASSERT(pFrom->GetItem()); - trans->PAppend("DELETE FROM guild_bank_item WHERE guildid = '%u'", m_Id); - trans->PAppend("DELETE FROM guild_bank_right WHERE guildid = '%u'", m_Id); - trans->PAppend("DELETE FROM guild_bank_eventlog WHERE guildid = '%u'", m_Id); - trans->PAppend("DELETE FROM guild_eventlog WHERE guildid = '%u'", m_Id); - CharacterDatabase.CommitTransaction(trans); - sObjectMgr.RemoveGuild(m_Id); + sScriptMgr.OnGuildItemMove(m_pGuild, m_pPlayer, pFrom->GetItem(), + pFrom->IsBank(), pFrom->GetContainer(), pFrom->GetSlotId(), + IsBank(), GetContainer(), GetSlotId()); } -void Guild::Roster(WorldSession *session /*= NULL*/) +inline void Guild::MoveItemData::CopySlots(SlotIds& ids) const { - // we can only guess size - WorldPacket data(SMSG_GUILD_ROSTER, (4+MOTD.length()+1+GINFO.length()+1+4+m_Ranks.size()*(4+4+GUILD_BANK_MAX_TABS*(4+4))+members.size()*50)); - data << (uint32)members.size(); - data << MOTD; - data << GINFO; + for (ItemPosCountVec::const_iterator itr = m_vec.begin(); itr != m_vec.end(); ++itr) + ids.insert(uint8(itr->pos)); +} - data << (uint32)m_Ranks.size(); - for (RankList::const_iterator ritr = m_Ranks.begin(); ritr != m_Ranks.end(); ++ritr) - { - data << uint32(ritr->Rights); - data << uint32(ritr->BankMoneyPerDay); // count of: withdraw gold(gold/day) Note: in game set gold, in packet set bronze. - for (int i = 0; i < GUILD_BANK_MAX_TABS; ++i) - { - data << uint32(ritr->TabRight[i]); // for TAB_i rights: view tabs = 0x01, deposit items =0x02 - data << uint32(ritr->TabSlotPerDay[i]); // for TAB_i count of: withdraw items(stack/day) - } - } - for (MemberList::const_iterator itr = members.begin(); itr != members.end(); ++itr) +/////////////////////////////////////////////////////////////////////////////// +// PlayerMoveItemData +bool Guild::PlayerMoveItemData::InitItem() +{ + m_pItem = m_pPlayer->GetItemByPos(m_container, m_slotId); + if (m_pItem) { - if (Player *pl = ObjectAccessor::FindPlayer(MAKE_NEW_GUID(itr->first, 0, HIGHGUID_PLAYER))) + // Anti-WPE protection. Do not move non-empty bags to bank. + if (m_pItem->IsBag() && !((Bag*)m_pItem)->IsEmpty()) { - data << uint64(pl->GetGUID()); - data << uint8(1); - data << pl->GetName(); - data << uint32(itr->second.RankId); - data << uint8(pl->getLevel()); - data << uint8(pl->getClass()); - data << uint8(0); // new 2.4.0 - data << uint32(pl->GetZoneId()); - data << itr->second.Pnote; - data << itr->second.OFFnote; + m_pPlayer->SendEquipError(EQUIP_ERR_CAN_ONLY_DO_WITH_EMPTY_BAGS, m_pItem); + m_pItem = NULL; } - else + // Bound items cannot be put into bank. + else if (!m_pItem->CanBeTraded()) { - data << uint64(MAKE_NEW_GUID(itr->first, 0, HIGHGUID_PLAYER)); - data << uint8(0); - data << itr->second.Name; - data << uint32(itr->second.RankId); - data << uint8(itr->second.Level); - data << uint8(itr->second.Class); - data << uint8(0); // new 2.4.0 - data << uint32(itr->second.ZoneId); - data << float(float(time(NULL)-itr->second.LogoutTime) / DAY); - data << itr->second.Pnote; - data << itr->second.OFFnote; + m_pPlayer->SendEquipError(EQUIP_ERR_ITEMS_CANT_BE_SWAPPED, m_pItem); + m_pItem = NULL; } } - if (session) - session->SendPacket(&data); - else - BroadcastPacket(&data); - sLog.outDebug("WORLD: Sent (SMSG_GUILD_ROSTER)"); + return (m_pItem != NULL); } -void Guild::Query(WorldSession *session) +void Guild::PlayerMoveItemData::RemoveItem(SQLTransaction& trans, MoveItemData* /*pOther*/, uint32 splitedAmount) { - WorldPacket data(SMSG_GUILD_QUERY_RESPONSE, (8*32+200));// we can only guess size - - data << uint32(m_Id); - data << m_Name; - - for (size_t i = 0 ; i < GUILD_RANKS_MAX_COUNT; ++i) // show always 10 ranks + if (splitedAmount) { - if (i < m_Ranks.size()) - data << m_Ranks[i].Name; - else - data << (uint8)0; // null string + m_pItem->SetCount(m_pItem->GetCount() - splitedAmount); + m_pItem->SetState(ITEM_CHANGED, m_pPlayer); + m_pPlayer->SaveInventoryAndGoldToDB(trans); + } + else + { + m_pPlayer->MoveItemFromInventory(m_container, m_slotId, true); + m_pItem->DeleteFromInventoryDB(trans); + m_pItem = NULL; } - - data << uint32(m_EmblemStyle); - data << uint32(m_EmblemColor); - data << uint32(m_BorderStyle); - data << uint32(m_BorderColor); - data << uint32(m_BackgroundColor); - data << uint32(0); // something new in WotLK - - session->SendPacket(&data); - sLog.outDebug("WORLD: Sent (SMSG_GUILD_QUERY_RESPONSE)"); } -void Guild::SetEmblem(uint32 emblemStyle, uint32 emblemColor, uint32 borderStyle, uint32 borderColor, uint32 backgroundColor) +Item* Guild::PlayerMoveItemData::StoreItem(SQLTransaction& trans, Item* pItem) { - m_EmblemStyle = emblemStyle; - m_EmblemColor = emblemColor; - m_BorderStyle = borderStyle; - m_BorderColor = borderColor; - m_BackgroundColor = backgroundColor; - - CharacterDatabase.PExecute("UPDATE guild SET EmblemStyle=%u, EmblemColor=%u, BorderStyle=%u, BorderColor=%u, BackgroundColor=%u WHERE guildid = %u", m_EmblemStyle, m_EmblemColor, m_BorderStyle, m_BorderColor, m_BackgroundColor, m_Id); + ASSERT(pItem); + m_pPlayer->MoveItemToInventory(m_vec, pItem, true); + m_pPlayer->SaveInventoryAndGoldToDB(trans); + return pItem; } -void Guild::UpdateLogoutTime(uint64 guid) +void Guild::PlayerMoveItemData::LogBankEvent(SQLTransaction& trans, MoveItemData* pFrom, uint32 count) const { - MemberList::iterator itr = members.find(GUID_LOPART(guid)); - if (itr == members.end()) - return; - - itr->second.LogoutTime = time(NULL); + ASSERT(pFrom); + // Bank -> Char + m_pGuild->_LogBankEvent(trans, GUILD_BANK_LOG_WITHDRAW_ITEM, pFrom->GetContainer(), m_pPlayer->GetGUIDLow(), + pFrom->GetItem()->GetEntry(), count); } -/** - * Updates the number of accounts that are in the guild - * A player may have many characters in the guild, but with the same account - */ -void Guild::UpdateAccountsNumber() +inline uint8 Guild::PlayerMoveItemData::_CanStore(Item* pItem, bool swap) { - // We use a set to be sure each element will be unique - std::set accountsIdSet; - for (MemberList::const_iterator itr = members.begin(); itr != members.end(); ++itr) - accountsIdSet.insert(itr->second.accountId); - - m_accountsNumber = accountsIdSet.size(); + return m_pPlayer->CanStoreItem(m_container, m_slotId, m_vec, pItem, swap); } -// ************************************************* -// Guild Eventlog part -// ************************************************* -// Display guild eventlog -void Guild::DisplayGuildEventLog(WorldSession *session) -{ - // Sending result - WorldPacket data(MSG_GUILD_EVENT_LOG_QUERY, 0); - // count, max count == 100 - data << uint8(m_GuildEventLog.size()); - for (GuildEventLog::const_iterator itr = m_GuildEventLog.begin(); itr != m_GuildEventLog.end(); ++itr) - { - // Event type - data << uint8(itr->EventType); - // Player 1 - data << uint64(itr->PlayerGuid1); - // Player 2 not for left/join guild events - if (itr->EventType != GUILD_EVENT_LOG_JOIN_GUILD && itr->EventType != GUILD_EVENT_LOG_LEAVE_GUILD) - data << uint64(itr->PlayerGuid2); - // New Rank - only for promote/demote guild events - if (itr->EventType == GUILD_EVENT_LOG_PROMOTE_PLAYER || itr->EventType == GUILD_EVENT_LOG_DEMOTE_PLAYER) - data << uint8(itr->NewRank); - // Event timestamp - data << uint32(time(NULL)-itr->TimeStamp); - } - session->SendPacket(&data); - sLog.outDebug("WORLD: Sent (MSG_GUILD_EVENT_LOG_QUERY)"); +/////////////////////////////////////////////////////////////////////////////// +// BankMoveItemData +bool Guild::BankMoveItemData::InitItem() +{ + m_pItem = m_pGuild->_GetItem(m_container, m_slotId); + return (m_pItem != NULL); } -// Add entry to guild eventlog -void Guild::LogGuildEvent(uint8 EventType, uint32 PlayerGuid1, uint32 PlayerGuid2, uint8 NewRank) +bool Guild::BankMoveItemData::HasStoreRights(MoveItemData* pOther) const { - GuildEventLogEntry NewEvent; - // Create event - NewEvent.EventType = EventType; - NewEvent.PlayerGuid1 = PlayerGuid1; - NewEvent.PlayerGuid2 = PlayerGuid2; - NewEvent.NewRank = NewRank; - NewEvent.TimeStamp = uint32(time(NULL)); - // Count new LogGuid - m_GuildEventLogNextGuid = (m_GuildEventLogNextGuid + 1) % sWorld.getIntConfig(CONFIG_GUILD_EVENT_LOG_COUNT); - // Check max records limit - if (m_GuildEventLog.size() >= GUILD_EVENTLOG_MAX_RECORDS) - m_GuildEventLog.pop_front(); - // Add event to list - m_GuildEventLog.push_back(NewEvent); - // Save event to DB - CharacterDatabase.PExecute("DELETE FROM guild_eventlog WHERE guildid='%u' AND LogGuid='%u'", m_Id, m_GuildEventLogNextGuid); - CharacterDatabase.PExecute("INSERT INTO guild_eventlog (guildid, LogGuid, EventType, PlayerGuid1, PlayerGuid2, NewRank, TimeStamp) VALUES ('%u','%u','%u','%u','%u','%u','" UI64FMTD "')", - m_Id, m_GuildEventLogNextGuid, uint32(NewEvent.EventType), NewEvent.PlayerGuid1, NewEvent.PlayerGuid2, uint32(NewEvent.NewRank), NewEvent.TimeStamp); + ASSERT(pOther); + // Do not check rights if item is being swapped within the same bank tab + if (pOther->IsBank() && pOther->GetContainer() == m_container) + return true; + return m_pGuild->_MemberHasTabRights(m_pPlayer->GetGUID(), m_container, GUILD_BANK_RIGHT_DEPOSIT_ITEM); } -// ************************************************* -// Guild Bank part -// ************************************************* -// Bank content related -void Guild::DisplayGuildBankContent(WorldSession *session, uint8 TabId) +bool Guild::BankMoveItemData::HasWithdrawRights(MoveItemData* pOther) const { - GuildBankTab const* tab = m_TabListMap[TabId]; + ASSERT(pOther); + // Do not check rights if item is being swapped within the same bank tab + if (pOther->IsBank() && pOther->GetContainer() == m_container) + return true; + return (m_pGuild->_GetMemberRemainingSlots(m_pPlayer->GetGUID(), m_container) != 0); +} - if (!IsMemberHaveRights(session->GetPlayer()->GetGUIDLow(), TabId, GUILD_BANK_RIGHT_VIEW_TAB)) - return; +void Guild::BankMoveItemData::RemoveItem(SQLTransaction& trans, MoveItemData* pOther, uint32 splitedAmount) +{ + ASSERT(m_pItem); + if (splitedAmount) + { + m_pItem->SetCount(m_pItem->GetCount() - splitedAmount); + m_pItem->FSetState(ITEM_CHANGED); + m_pItem->SaveToDB(trans); + } + else + { + m_pGuild->_RemoveItem(trans, m_container, m_slotId); + m_pItem = NULL; + } + // Decrease amount of player's remaining items (if item is moved to different tab or to player) + if (!pOther->IsBank() || pOther->GetContainer() != m_container) + m_pGuild->_DecreaseMemberRemainingSlots(trans, m_pPlayer->GetGUID(), m_container); +} - WorldPacket data(SMSG_GUILD_BANK_LIST, 1200); +Item* Guild::BankMoveItemData::StoreItem(SQLTransaction& trans, Item* pItem) +{ + if (!pItem) + return NULL; - data << uint64(GetGuildBankMoney()); - data << uint8(TabId); - data << uint32(GetMemberSlotWithdrawRem(session->GetPlayer()->GetGUIDLow(), TabId)); // remaining slots for today - data << uint8(0); // Tell client that there's no tab info in this packet + BankTab* pTab = m_pGuild->GetBankTab(m_container); + if (!pTab) + return NULL; - data << uint8(GUILD_BANK_MAX_SLOTS); + Item* pLastItem = pItem; + for (ItemPosCountVec::const_iterator itr = m_vec.begin(); itr != m_vec.end(); ) + { + ItemPosCount pos(*itr); + ++itr; - for (uint8 i=0; i < GUILD_BANK_MAX_SLOTS; ++i) - AppendDisplayGuildBankSlot(data, tab, i); + sLog.outDebug("GUILD STORAGE: StoreItem tab = %u, slot = %u, item = %u, count = %u", + m_container, m_slotId, pItem->GetEntry(), pItem->GetCount()); + pLastItem = _StoreItem(trans, pTab, pItem, pos, itr != m_vec.end()); + } + return pLastItem; +} - session->SendPacket(&data); +void Guild::BankMoveItemData::LogBankEvent(SQLTransaction& trans, MoveItemData* pFrom, uint32 count) const +{ + ASSERT(pFrom->GetItem()); + if (pFrom->IsBank()) + // Bank -> Bank + m_pGuild->_LogBankEvent(trans, GUILD_BANK_LOG_MOVE_ITEM, pFrom->GetContainer(), m_pPlayer->GetGUIDLow(), + pFrom->GetItem()->GetEntry(), count, m_container); + else + // Char -> Bank + m_pGuild->_LogBankEvent(trans, GUILD_BANK_LOG_DEPOSIT_ITEM, m_container, m_pPlayer->GetGUIDLow(), + pFrom->GetItem()->GetEntry(), count); +} - sLog.outDebug("WORLD: Sent (SMSG_GUILD_BANK_LIST)"); +void Guild::BankMoveItemData::LogAction(MoveItemData* pFrom) const +{ + MoveItemData::LogAction(pFrom); + if (!pFrom->IsBank() && sWorld.getBoolConfig(CONFIG_GM_LOG_TRADE) && m_pPlayer->GetSession()->GetSecurity() > SEC_PLAYER) // TODO: move to scripts + sLog.outCommand(m_pPlayer->GetSession()->GetAccountId(), + "GM %s (Account: %u) deposit item: %s (Entry: %d Count: %u) to guild bank (Guild ID: %u)", + m_pPlayer->GetName(), m_pPlayer->GetSession()->GetAccountId(), + pFrom->GetItem()->GetProto()->Name1, pFrom->GetItem()->GetEntry(), pFrom->GetItem()->GetCount(), + m_pGuild->GetId()); } -void Guild::DisplayGuildBankContentUpdate(uint8 TabId, int32 slot1, int32 slot2) +Item* Guild::BankMoveItemData::_StoreItem(SQLTransaction& trans, BankTab* pTab, Item *pItem, ItemPosCount& pos, bool clone) const { - GuildBankTab const* tab = m_TabListMap[TabId]; + uint8 slotId = uint8(pos.pos); + uint32 count = pos.count; + if (Item* pItemDest = pTab->GetItem(slotId)) + { + pItemDest->SetCount(pItemDest->GetCount() + count); + pItemDest->FSetState(ITEM_CHANGED); + pItemDest->SaveToDB(trans); + if (!clone) + { + pItem->RemoveFromWorld(); + pItem->DeleteFromDB(trans); + delete pItem; + } + return pItemDest; + } - WorldPacket data(SMSG_GUILD_BANK_LIST, 1200); + if (clone) + pItem = pItem->CloneItem(count); + else + pItem->SetCount(count); - data << uint64(GetGuildBankMoney()); - data << uint8(TabId); + if (pItem && pTab->SetItem(trans, slotId, pItem)) + return pItem; - size_t rempos = data.wpos(); - data << uint32(0); // item withdraw amount, will be filled later - data << uint8(0); // Tell client that there's no tab info in this packet + return NULL; +} - if (slot2 == -1) // single item in slot1 +// Tries to reserve space for source item. +// If item in destination slot exists it must be the item of the same entry +// and stack must have enough space to take at least one item. +// Returns false if destination item specified and it cannot be used to reserve space. +bool Guild::BankMoveItemData::_ReserveSpace(uint8 slotId, Item* pItem, Item* pItemDest, uint32& count) +{ + uint32 requiredSpace = pItem->GetMaxStackCount(); + if (pItemDest) { - data << uint8(1); // item count - - AppendDisplayGuildBankSlot(data, tab, slot1); + // Make sure source and destination items match and destination item has space for more stacks. + if (pItemDest->GetEntry() != pItem->GetEntry() || pItemDest->GetCount() >= pItem->GetMaxStackCount()) + return false; + requiredSpace -= pItemDest->GetCount(); } - else // 2 items (in slot1 and slot2) - { - data << uint8(2); // item count + // Let's not be greedy, reserve only required space + requiredSpace = std::min(requiredSpace, count); - if (slot1 > slot2) - std::swap(slot1,slot2); - - AppendDisplayGuildBankSlot(data, tab, slot1); - AppendDisplayGuildBankSlot(data, tab, slot2); + // Reserve space + ItemPosCount pos(slotId, requiredSpace); + if (!pos.isContainedIn(m_vec)) + { + m_vec.push_back(pos); + count -= requiredSpace; } + return true; +} - for (MemberList::const_iterator itr = members.begin(); itr != members.end(); ++itr) +void Guild::BankMoveItemData::_CanStoreItemInTab(Item* pItem, uint8 skipSlotId, bool merge, uint32& count) +{ + for (uint8 slotId = 0; (slotId < GUILD_BANK_MAX_SLOTS) && (count > 0); ++slotId) { - Player *player = ObjectAccessor::FindPlayer(MAKE_NEW_GUID(itr->first, 0, HIGHGUID_PLAYER)); - if (!player) + // Skip slot already processed in _CanStore (when destination slot was specified) + if (slotId == skipSlotId) continue; - if (!IsMemberHaveRights(itr->first,TabId,GUILD_BANK_RIGHT_VIEW_TAB)) - continue; + Item* pItemDest = m_pGuild->_GetItem(m_container, slotId); + if (pItemDest == pItem) + pItemDest = NULL; - data.put(rempos,uint32(GetMemberSlotWithdrawRem(player->GetGUIDLow(), TabId))); + // If merge skip empty, if not merge skip non-empty + if ((pItemDest != NULL) != merge) + continue; - player->GetSession()->SendPacket(&data); + _ReserveSpace(slotId, pItem, pItemDest, count); } - - sLog.outDebug("WORLD: Sent (SMSG_GUILD_BANK_LIST)"); } -void Guild::DisplayGuildBankContentUpdate(uint8 TabId, GuildItemPosCountVec const& slots) +uint8 Guild::BankMoveItemData::_CanStore(Item* pItem, bool swap) { - GuildBankTab const* tab = m_TabListMap[TabId]; - - WorldPacket data(SMSG_GUILD_BANK_LIST, 1200); + sLog.outDebug("GUILD STORAGE: CanStore() tab = %u, slot = %u, item = %u, count = %u", + m_container, m_slotId, pItem->GetEntry(), pItem->GetCount()); - data << uint64(GetGuildBankMoney()); - data << uint8(TabId); - - size_t rempos = data.wpos(); - data << uint32(0); // item withdraw amount, will be filled later - data << uint8(0); // Tell client that there's no tab info in this packet - - data << uint8(slots.size()); // updates count + uint32 count = pItem->GetCount(); + // Soulbound items cannot be moved + if (pItem->IsSoulBound()) + return EQUIP_ERR_CANT_DROP_SOULBOUND; - for (GuildItemPosCountVec::const_iterator itr = slots.begin(); itr != slots.end(); ++itr) - AppendDisplayGuildBankSlot(data, tab, itr->Slot); + // Make sure destination bank tab exists + if (m_container >= m_pGuild->_GetPurchasedTabsSize()) + return EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG; - for (MemberList::const_iterator itr = members.begin(); itr != members.end(); ++itr) + // Slot explicitely specified. Check it. + if (m_slotId != NULL_SLOT) { - Player *player = ObjectAccessor::FindPlayer(MAKE_NEW_GUID(itr->first, 0, HIGHGUID_PLAYER)); - if (!player) - continue; + Item* pItemDest = m_pGuild->_GetItem(m_container, m_slotId); + // Ignore swapped item (this slot will be empty after move) + if ((pItemDest == pItem) || swap) + pItemDest = NULL; - if (!IsMemberHaveRights(itr->first,TabId,GUILD_BANK_RIGHT_VIEW_TAB)) - continue; + if (!_ReserveSpace(m_slotId, pItem, pItemDest, count)) + return EQUIP_ERR_ITEM_CANT_STACK; - data.put(rempos,uint32(GetMemberSlotWithdrawRem(player->GetGUIDLow(), TabId))); + if (count == 0) + return EQUIP_ERR_OK; + } - player->GetSession()->SendPacket(&data); + // Slot was not specified or it has not enough space for all the items in stack + // Search for stacks to merge with + if (pItem->GetMaxStackCount() > 1) + { + _CanStoreItemInTab(pItem, m_slotId, true, count); + if (count == 0) + return EQUIP_ERR_OK; } - sLog.outDebug("WORLD: Sent (SMSG_GUILD_BANK_LIST)"); + // Search free slot for item + _CanStoreItemInTab(pItem, m_slotId, false, count); + if (count == 0) + return EQUIP_ERR_OK; + + return EQUIP_ERR_BANK_FULL; } -Item* Guild::GetItem(uint8 TabId, uint8 SlotId) +/////////////////////////////////////////////////////////////////////////////// +// Guild +Guild::Guild() : m_id(0), m_leaderGuid(0), m_createdDate(0), m_accountsNumber(0), m_bankMoney(0), m_eventLog(NULL) { - if (TabId >= GetPurchasedTabs() || SlotId >= GUILD_BANK_MAX_SLOTS) - return NULL; - return m_TabListMap[TabId]->Slots[SlotId]; + memset(&m_bankEventLog, 0, (GUILD_BANK_MAX_TABS + 1) * sizeof(LogHolder*)); } -// ************************************************* -// Tab related - -void Guild::DisplayGuildBankTabsInfo(WorldSession *session) +Guild::~Guild() { - WorldPacket data(SMSG_GUILD_BANK_LIST, 500); - - data << uint64(GetGuildBankMoney()); - data << uint8(0); // TabInfo packet must be for TabId 0 - data << uint32(GetMemberSlotWithdrawRem(session->GetPlayer()->GetGUIDLow(), 0)); - data << uint8(1); // Tell client that this packet includes tab info - data << uint8(GetPurchasedTabs()); // here is the number of tabs - - for (uint8 i = 0; i < GetPurchasedTabs(); ++i) - { - data << m_TabListMap[i]->Name.c_str(); - data << m_TabListMap[i]->Icon.c_str(); - } - data << uint8(0); // Do not send tab content - session->SendPacket(&data); + SQLTransaction temp(NULL); + _DeleteBankItems(temp); - sLog.outDebug("WORLD: Sent (SMSG_GUILD_BANK_LIST)"); + // Cleanup + if (m_eventLog) + delete m_eventLog; + for (uint8 tabId = 0; tabId <= GUILD_BANK_MAX_TABS; ++tabId) + if (m_bankEventLog[tabId]) + delete m_bankEventLog[tabId]; + for (Members::iterator itr = m_members.begin(); itr != m_members.end(); ++itr) + delete itr->second; } -void Guild::DisplayGuildBankMoneyUpdate(WorldSession *session) +// Creates new guild with default data and saves it to database. +bool Guild::Create(Player* pLeader, const std::string& name) { - WorldPacket data(SMSG_GUILD_BANK_LIST, 8+1+4+1+1); - - data << uint64(GetGuildBankMoney()); - data << uint8(0); // TabId, default 0 - data << uint32(GetMemberSlotWithdrawRem(session->GetPlayer()->GetGUIDLow(), 0)); - data << uint8(0); // Tell that there's no tab info in this packet - data << uint8(0); // not send items - BroadcastPacket(&data); + // Check if guild with such name already exists + if (sObjectMgr.GetGuildByName(name)) + return false; - sLog.outDebug("WORLD: Sent (SMSG_GUILD_BANK_LIST)"); -} + WorldSession* pLeaderSession = pLeader->GetSession(); + if (!pLeaderSession) + return false; -void Guild::CreateNewBankTab() -{ - if (GetPurchasedTabs() >= GUILD_BANK_MAX_TABS) - return; + m_id = sObjectMgr.GenerateGuildId(); + m_leaderGuid = pLeader->GetGUID(); + m_name = name; + m_info = ""; + m_motd = "No message set."; + m_bankMoney = 0; + m_createdDate = ::time(NULL); + _CreateLogHolders(); - uint32 tabId = GetPurchasedTabs(); // next free id - m_TabListMap.push_back(new GuildBankTab); + sLog.outDebug("GUILD: creating guild [%s] for leader %s (%u)", + name.c_str(), pLeader->GetName(), GUID_LOPART(m_leaderGuid)); + PreparedStatement* stmt = NULL; SQLTransaction trans = CharacterDatabase.BeginTransaction(); - trans->PAppend("DELETE FROM guild_bank_tab WHERE guildid='%u' AND TabId='%u'", m_Id, tabId); - trans->PAppend("INSERT INTO guild_bank_tab (guildid,TabId) VALUES ('%u','%u')", m_Id, tabId); + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_MEMBERS); + stmt->setUInt32(0, m_id); + trans->Append(stmt); + + uint8 index = 0; + stmt = CharacterDatabase.GetPreparedStatement(CHAR_ADD_GUILD); + stmt->setUInt32( index, m_id); + stmt->setString(++index, name); + stmt->setUInt32(++index, GUID_LOPART(m_leaderGuid)); + stmt->setString(++index, m_info); + stmt->setString(++index, m_motd); + stmt->setUInt64(++index, uint64(m_createdDate)); + stmt->setUInt32(++index, m_emblemInfo.GetStyle()); + stmt->setUInt32(++index, m_emblemInfo.GetColor()); + stmt->setUInt32(++index, m_emblemInfo.GetBorderStyle()); + stmt->setUInt32(++index, m_emblemInfo.GetBorderColor()); + stmt->setUInt32(++index, m_emblemInfo.GetBackgroundColor()); + stmt->setUInt64(++index, m_bankMoney); + trans->Append(stmt); + CharacterDatabase.CommitTransaction(trans); + // Create default ranks + _CreateDefaultGuildRanks(pLeaderSession->GetSessionDbLocaleIndex()); + // Add guildmaster + bool ret = AddMember(m_leaderGuid, GR_GUILDMASTER); + if (ret) + // Call scripts on successful create + sScriptMgr.OnGuildCreate(this, pLeader, name); + + return ret; } -void Guild::SetGuildBankTabInfo(uint8 TabId, std::string Name, std::string Icon) +// Disbands guild and deletes all related data from database +void Guild::Disband() { - if (m_TabListMap[TabId]->Name == Name && m_TabListMap[TabId]->Icon == Icon) - return; + // Call scripts before guild data removed from database + sScriptMgr.OnGuildDisband(this); - m_TabListMap[TabId]->Name = Name; - m_TabListMap[TabId]->Icon = Icon; + _BroadcastEvent(GE_DISBANDED, 0); + // Remove all members + while (!m_members.empty()) + { + Members::const_iterator itr = m_members.begin(); + DeleteMember(itr->second->GetGUID(), true); + } - CharacterDatabase.escape_string(Name); - CharacterDatabase.escape_string(Icon); - CharacterDatabase.PExecute("UPDATE guild_bank_tab SET TabName='%s',TabIcon='%s' WHERE guildid='%u' AND TabId='%u'", Name.c_str(), Icon.c_str(), m_Id, uint32(TabId)); -} + PreparedStatement* stmt = NULL; + SQLTransaction trans = CharacterDatabase.BeginTransaction(); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD); + stmt->setUInt32(0, m_id); + trans->Append(stmt); -uint32 Guild::GetBankRights(uint32 rankId, uint8 TabId) const -{ - if (rankId >= m_Ranks.size() || TabId >= GUILD_BANK_MAX_TABS) - return 0; + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_RANKS); + stmt->setUInt32(0, m_id); + trans->Append(stmt); - return m_Ranks[rankId].TabRight[TabId]; -} + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_BANK_TABS); + stmt->setUInt32(0, m_id); + trans->Append(stmt); -// ************************************************* -// Money deposit/withdraw related + // Free bank tab used memory and delete items stored in them + _DeleteBankItems(trans, true); -void Guild::SendMoneyInfo(WorldSession *session, uint32 LowGuid) -{ - WorldPacket data(MSG_GUILD_BANK_MONEY_WITHDRAWN, 4); - data << uint32(GetMemberMoneyWithdrawRem(LowGuid)); - session->SendPacket(&data); - sLog.outDebug("WORLD: Sent MSG_GUILD_BANK_MONEY_WITHDRAWN"); -} + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_BANK_ITEMS); + stmt->setUInt32(0, m_id); + trans->Append(stmt); -bool Guild::MemberMoneyWithdraw(uint32 amount, uint32 LowGuid, SQLTransaction& trans) -{ - uint32 MoneyWithDrawRight = GetMemberMoneyWithdrawRem(LowGuid); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_BANK_RIGHTS); + stmt->setUInt32(0, m_id); + trans->Append(stmt); - if (MoneyWithDrawRight < amount || GetGuildBankMoney() < amount) - return false; + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_BANK_EVENTLOGS); + stmt->setUInt32(0, m_id); + trans->Append(stmt); - SetBankMoney(GetGuildBankMoney()-amount, trans); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_EVENTLOGS); + stmt->setUInt32(0, m_id); + trans->Append(stmt); - if (MoneyWithDrawRight < WITHDRAW_MONEY_UNLIMITED) - { - MemberList::iterator itr = members.find(LowGuid); - if (itr == members.end()) - return false; - itr->second.BankRemMoney -= amount; - trans->PAppend("UPDATE guild_member SET BankRemMoney='%u' WHERE guildid='%u' AND guid='%u'", - itr->second.BankRemMoney, m_Id, LowGuid); - } - return true; + CharacterDatabase.CommitTransaction(trans); + sObjectMgr.RemoveGuild(m_id); } -void Guild::SetBankMoney(int64 money, SQLTransaction& trans) +/////////////////////////////////////////////////////////////////////////////// +// HANDLE CLIENT COMMANDS +void Guild::HandleRoster(WorldSession *session /*= NULL*/) { - if (money < 0) // I don't know how this happens, it does!! - money = 0; - m_GuildBankMoney = money; + // Guess size + WorldPacket data(SMSG_GUILD_ROSTER, (4 + m_motd.length() + 1 + m_info.length() + 1 + 4 + _GetRanksSize() * (4 + 4 + GUILD_BANK_MAX_TABS * (4 + 4)) + m_members.size() * 50)); + data << uint32(m_members.size()); + data << m_motd; + data << m_info; - trans->PAppend("UPDATE guild SET BankMoney='" UI64FMTD "' WHERE guildid='%u'", money, m_Id); -} + data << uint32(_GetRanksSize()); + for (Ranks::const_iterator ritr = m_ranks.begin(); ritr != m_ranks.end(); ++ritr) + ritr->WritePacket(data); -// ************************************************* -// Item per day and money per day related + for (Members::const_iterator itr = m_members.begin(); itr != m_members.end(); ++itr) + itr->second->WritePacket(data); + + if (session) + session->SendPacket(&data); + else + BroadcastPacket(&data); + sLog.outDebug("WORLD: Sent (SMSG_GUILD_ROSTER)"); +} -bool Guild::MemberItemWithdraw(uint8 TabId, uint32 LowGuid, SQLTransaction& trans) +void Guild::HandleQuery(WorldSession *session) { - uint32 SlotsWithDrawRight = GetMemberSlotWithdrawRem(LowGuid, TabId); + WorldPacket data(SMSG_GUILD_QUERY_RESPONSE, 8 * 32 + 200); // Guess size - if (SlotsWithDrawRight == 0) - return false; + data << uint32(m_id); + data << m_name; - if (SlotsWithDrawRight < WITHDRAW_SLOT_UNLIMITED) + for (uint8 i = 0 ; i < GUILD_RANKS_MAX_COUNT; ++i) // Alwayse show 10 ranks { - MemberList::iterator itr = members.find(LowGuid); - if (itr == members.end()) - return false; - --itr->second.BankRemSlotsTab[TabId]; - trans->PAppend("UPDATE guild_member SET BankRemSlotsTab%u='%u' WHERE guildid='%u' AND guid='%u'", - uint32(TabId), itr->second.BankRemSlotsTab[TabId], m_Id, LowGuid); + if (i < _GetRanksSize()) + data << m_ranks[i].GetName(); + else + data << uint8(0); // Empty string } - return true; -} - -bool Guild::IsMemberHaveRights(uint32 LowGuid, uint8 TabId, uint32 rights) const -{ - MemberList::const_iterator itr = members.find(LowGuid); - if (itr == members.end()) - return false; - if (itr->second.RankId == GR_GUILDMASTER) - return true; + m_emblemInfo.WritePacket(data); + data << uint32(0); // Something new in WotLK - return (GetBankRights(itr->second.RankId,TabId) & rights) == rights; + session->SendPacket(&data); + sLog.outDebug("WORLD: Sent (SMSG_GUILD_QUERY_RESPONSE)"); } -uint32 Guild::GetMemberSlotWithdrawRem(uint32 LowGuid, uint8 TabId) +void Guild::HandleSetMOTD(WorldSession* session, const std::string& motd) { - MemberList::iterator itr = members.find(LowGuid); - if (itr == members.end()) - return 0; + if (m_motd == motd) + return; - if (itr->second.RankId == GR_GUILDMASTER) - return WITHDRAW_SLOT_UNLIMITED; + // Player must have rights to set MOTD + if (!_HasRankRight(session->GetPlayer(), GR_RIGHT_SETMOTD)) + SendCommandResult(session, GUILD_INVITE_S, ERR_GUILD_PERMISSIONS); + else + { + m_motd = motd; - if ((GetBankRights(itr->second.RankId,TabId) & GUILD_BANK_RIGHT_VIEW_TAB) != GUILD_BANK_RIGHT_VIEW_TAB) - return 0; + sScriptMgr.OnGuildMOTDChanged(this, motd); - uint32 curTime = uint32(time(NULL)/MINUTE); - if (curTime - itr->second.BankResetTimeTab[TabId] >= 24*HOUR/MINUTE) - { - itr->second.BankResetTimeTab[TabId] = curTime; - itr->second.BankRemSlotsTab[TabId] = GetBankSlotPerDay(itr->second.RankId, TabId); - CharacterDatabase.PExecute("UPDATE guild_member SET BankResetTimeTab%u='%u', BankRemSlotsTab%u='%u' WHERE guildid='%u' AND guid='%u'", - uint32(TabId), itr->second.BankResetTimeTab[TabId], uint32(TabId), itr->second.BankRemSlotsTab[TabId], m_Id, LowGuid); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SET_GUILD_MOTD); + stmt->setString(0, motd); + stmt->setUInt32(1, m_id); + CharacterDatabase.Execute(stmt); + + _BroadcastEvent(GE_MOTD, 0, motd.c_str()); } - return itr->second.BankRemSlotsTab[TabId]; } -uint32 Guild::GetMemberMoneyWithdrawRem(uint32 LowGuid) +void Guild::HandleSetInfo(WorldSession* session, const std::string& info) { - MemberList::iterator itr = members.find(LowGuid); - if (itr == members.end()) - return 0; - - if (itr->second.RankId == GR_GUILDMASTER) - return WITHDRAW_MONEY_UNLIMITED; + if (m_info == info) + return; - uint32 curTime = uint32(time(NULL)/MINUTE); // minutes - // 24 hours - if (curTime > itr->second.BankResetTimeMoney + 24*HOUR/MINUTE) + // Player must have rights to set guild's info + if (!_HasRankRight(session->GetPlayer(), GR_RIGHT_MODIFY_GUILD_INFO)) + SendCommandResult(session, GUILD_CREATE_S, ERR_GUILD_PERMISSIONS); + else { - itr->second.BankResetTimeMoney = curTime; - itr->second.BankRemMoney = GetBankMoneyPerDay(itr->second.RankId); - CharacterDatabase.PExecute("UPDATE guild_member SET BankResetTimeMoney='%u', BankRemMoney='%u' WHERE guildid='%u' AND guid='%u'", - itr->second.BankResetTimeMoney, itr->second.BankRemMoney, m_Id, LowGuid); + m_info = info; + + sScriptMgr.OnGuildInfoChanged(this, info); + + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SET_GUILD_INFO); + stmt->setString(0, info); + stmt->setUInt32(1, m_id); + CharacterDatabase.Execute(stmt); } - return itr->second.BankRemMoney; } -void Guild::SetBankMoneyPerDay(uint32 rankId, uint32 money) +void Guild::HandleSetEmblem(WorldSession* session, const EmblemInfo& emblemInfo) { - if (rankId >= m_Ranks.size()) - return; - - if (rankId == GR_GUILDMASTER) - money = WITHDRAW_MONEY_UNLIMITED; + Player* player = session->GetPlayer(); + if (!_IsLeader(player)) + // "Only pGuild leaders can create emblems." + SendSaveEmblemResult(session, ERR_GUILDEMBLEM_NOTGUILDMASTER); + else if (!player->HasEnoughMoney(EMBLEM_PRICE)) + // "You can't afford to do that." + SendSaveEmblemResult(session, ERR_GUILDEMBLEM_NOTENOUGHMONEY); + else + { + player->ModifyMoney(-int32(EMBLEM_PRICE)); - m_Ranks[rankId].BankMoneyPerDay = money; + m_emblemInfo = emblemInfo; + m_emblemInfo.SaveToDB(m_id); - for (MemberList::iterator itr = members.begin(); itr != members.end(); ++itr) - if (itr->second.RankId == rankId) - itr->second.BankResetTimeMoney = 0; + // "Guild Emblem saved." + SendSaveEmblemResult(session, ERR_GUILDEMBLEM_SUCCESS); - CharacterDatabase.PExecute("UPDATE guild_rank SET BankMoneyPerDay='%u' WHERE rid='%u' AND guildid='%u'", money, rankId, m_Id); - CharacterDatabase.PExecute("UPDATE guild_member SET BankResetTimeMoney='0' WHERE guildid='%u' AND rank='%u'", m_Id, rankId); + HandleQuery(session); + } } -void Guild::SetBankRightsAndSlots(uint32 rankId, uint8 TabId, uint32 right, uint32 nbSlots, bool db) +void Guild::HandleSetLeader(WorldSession* session, const std::string& name) { - if (rankId >= m_Ranks.size() || TabId >= GetPurchasedTabs()) - return; - - if (rankId == GR_GUILDMASTER) + Player* player = session->GetPlayer(); + // Only leader can assign new leader + if (!_IsLeader(player)) + SendCommandResult(session, GUILD_INVITE_S, ERR_GUILD_PERMISSIONS); + // Old leader must be a member of guild + else if (Member* pOldLeader = GetMember(player->GetGUID())) { - nbSlots = WITHDRAW_SLOT_UNLIMITED; - right = GUILD_BANK_RIGHT_FULL; + // New leader must be a member of guild + if (Member* pNewLeader = GetMember(session, name)) + { + _SetLeaderGUID(pNewLeader); + pOldLeader->ChangeRank(GR_OFFICER); + _BroadcastEvent(GE_LEADER_CHANGED, 0, player->GetName(), name.c_str()); + } } + else + SendCommandResult(session, GUILD_INVITE_S, ERR_GUILD_PERMISSIONS); +} - m_Ranks[rankId].TabSlotPerDay[TabId] = nbSlots; - m_Ranks[rankId].TabRight[TabId] = right; - - if (db) +void Guild::HandleSetBankTabInfo(WorldSession* session, uint8 tabId, const std::string& name, const std::string& icon) +{ + if (BankTab* pTab = GetBankTab(tabId)) { - for (MemberList::iterator itr = members.begin(); itr != members.end(); ++itr) - if (itr->second.RankId == rankId) - for (uint8 i = 0; i < GUILD_BANK_MAX_TABS; ++i) - itr->second.BankResetTimeTab[i] = 0; - - CharacterDatabase.PExecute("DELETE FROM guild_bank_right WHERE guildid='%u' AND TabId='%u' AND rid='%u'", m_Id, uint32(TabId), rankId); - CharacterDatabase.PExecute("INSERT INTO guild_bank_right (guildid,TabId,rid,gbright,SlotPerDay) VALUES " - "('%u','%u','%u','%u','%u')", m_Id, uint32(TabId), rankId, m_Ranks[rankId].TabRight[TabId], m_Ranks[rankId].TabSlotPerDay[TabId]); - CharacterDatabase.PExecute("UPDATE guild_member SET BankResetTimeTab%u='0' WHERE guildid='%u' AND rank='%u'", uint32(TabId), m_Id, rankId); + pTab->SetInfo(name, icon); + SendBankTabsInfo(session); + _SendBankContent(session, tabId); } } -uint32 Guild::GetBankMoneyPerDay(uint32 rankId) +void Guild::HandleSetMemberNote(WorldSession* session, const std::string& name, const std::string& note, bool officer) { - if (rankId >= m_Ranks.size()) - return 0; - - if (rankId == GR_GUILDMASTER) - return WITHDRAW_MONEY_UNLIMITED; - return m_Ranks[rankId].BankMoneyPerDay; + // Player must have rights to set public/officer note + if (!_HasRankRight(session->GetPlayer(), officer ? GR_RIGHT_EOFFNOTE : GR_RIGHT_EPNOTE)) + SendCommandResult(session, GUILD_INVITE_S, ERR_GUILD_PERMISSIONS); + // Noted player must be a member of guild + else if (Member* pMember = GetMember(session, name)) + { + if (officer) + pMember->SetOfficerNote(note); + else + pMember->SetPublicNote(note); + HandleRoster(session); + } } -uint32 Guild::GetBankSlotPerDay(uint32 rankId, uint8 TabId) +void Guild::HandleSetRankInfo(WorldSession* session, uint8 rankId, const std::string& name, uint32 rights, uint32 moneyPerDay, GuildBankRightsAndSlotsVec rightsAndSlots) { - if (rankId >= m_Ranks.size() || TabId >= GUILD_BANK_MAX_TABS) - return 0; + // Only leader can modify ranks + if (!_IsLeader(session->GetPlayer())) + SendCommandResult(session, GUILD_INVITE_S, ERR_GUILD_PERMISSIONS); + else if (RankInfo* rankInfo = GetRankInfo(rankId)) + { + sLog.outDebug("WORLD: Changed RankName to '%s', rights to 0x%08X", name.c_str(), rights); - if (rankId == GR_GUILDMASTER) - return WITHDRAW_SLOT_UNLIMITED; - return m_Ranks[rankId].TabSlotPerDay[TabId]; -} + rankInfo->SetName(name); + rankInfo->SetRights(rights); + _SetRankBankMoneyPerDay(rankId, moneyPerDay); -// ************************************************* -// Rights per day related + uint8 tabId = 0; + for (GuildBankRightsAndSlotsVec::const_iterator itr = rightsAndSlots.begin(); itr != rightsAndSlots.end(); ++itr) + _SetRankBankTabRightsAndSlots(rankId, tabId++, *itr); -bool Guild::LoadBankRightsFromDB(QueryResult guildBankTabRightsResult) -{ - if (!guildBankTabRightsResult) - return true; + HandleQuery(session); + HandleRoster(); // Broadcast for tab rights update + } +} - do - { - Field *fields = guildBankTabRightsResult->Fetch(); - //prevent crash when all rights in result are already processed - if (!fields) - break; - uint32 guildId = fields[0].GetUInt32(); - if (guildId < m_Id) - { - //there is in table guild_bank_right record which doesn't have guildid in guild table, report error - sLog.outErrorDb("Guild %u does not exist but it has a record in guild_bank_right table, deleting it!", guildId); - CharacterDatabase.PExecute("DELETE FROM guild_bank_right WHERE guildid = '%u'", guildId); - continue; - } +void Guild::HandleBuyBankTab(WorldSession* session, uint8 tabId) +{ + if (tabId != _GetPurchasedTabsSize()) + return; - if (guildId > m_Id) - //we loaded all ranks for this guild bank already, break cycle - break; - uint8 TabId = fields[1].GetUInt8(); - uint32 rankId = fields[2].GetUInt32(); - uint16 right = fields[3].GetUInt16(); - uint16 SlotPerDay = fields[4].GetUInt16(); + uint32 tabCost = _GetGuildBankTabPrice(tabId) * GOLD; + if (!tabCost) + return; - SetBankRightsAndSlots(rankId, TabId, right, SlotPerDay, false); + Player* player = session->GetPlayer(); + if (!player->HasEnoughMoney(tabCost)) // Should not happen, this is checked by client + return; - }while (guildBankTabRightsResult->NextRow()); + if (!_CreateNewBankTab()) + return; - return true; + player->ModifyMoney(-int32(tabCost)); + _SetRankBankMoneyPerDay(player->GetRank(), GUILD_WITHDRAW_MONEY_UNLIMITED); + _SetRankBankTabRightsAndSlots(player->GetRank(), tabId, GuildBankRightsAndSlots(GUILD_BANK_RIGHT_FULL, GUILD_WITHDRAW_SLOT_UNLIMITED)); + HandleRoster(); // Broadcast for tab rights update + SendBankTabsInfo(session); } -// ************************************************* -// Bank log related - - -void Guild::DisplayGuildBankLogs(WorldSession *session, uint8 TabId) +void Guild::HandleInviteMember(WorldSession* session, const std::string& name) { - if (TabId > GUILD_BANK_MAX_TABS) // tabs starts in 0 + Player* pInvitee = sObjectAccessor.FindPlayerByName(name.c_str()); + if (!pInvitee) + { + SendCommandResult(session, GUILD_INVITE_S, ERR_GUILD_PLAYER_NOT_FOUND_S, name); return; + } - if (TabId == GUILD_BANK_MAX_TABS) + Player* player = session->GetPlayer(); + // Do not show invitations from ignored players + if (pInvitee->GetSocial()->HasIgnore(player->GetGUIDLow())) + return; + if (!sWorld.getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GUILD) && pInvitee->GetTeam() != player->GetTeam()) { - // Here we display money logs - WorldPacket data(MSG_GUILD_BANK_LOG_QUERY, m_GuildBankEventLog_Money.size()*(4*4+1)+1+1); - data << uint8(TabId); // Here GUILD_BANK_MAX_TABS - data << uint8(m_GuildBankEventLog_Money.size()); // number of log entries - for (GuildBankEventLog::const_iterator itr = m_GuildBankEventLog_Money.begin(); itr != m_GuildBankEventLog_Money.end(); ++itr) - { - data << uint8(itr->EventType); - data << uint64(MAKE_NEW_GUID(itr->PlayerGuid,0,HIGHGUID_PLAYER)); - data << uint32(itr->ItemOrMoney); - data << uint32(time(NULL) - itr->TimeStamp); - } - session->SendPacket(&data); + SendCommandResult(session, GUILD_INVITE_S, ERR_GUILD_NOT_ALLIED, name); + return; } - else + // Invited player cannot be in another guild + if (pInvitee->GetGuildId()) { - // here we display current tab logs - WorldPacket data(MSG_GUILD_BANK_LOG_QUERY, m_GuildBankEventLog_Item[TabId].size()*(4*4+1+1)+1+1); - data << uint8(TabId); // Here a real Tab Id - data << uint8(m_GuildBankEventLog_Item[TabId].size()); // number of log entries - for (GuildBankEventLog::const_iterator itr = m_GuildBankEventLog_Item[TabId].begin(); itr != m_GuildBankEventLog_Item[TabId].end(); ++itr) - { - data << uint8(itr->EventType); - data << uint64(MAKE_NEW_GUID(itr->PlayerGuid,0,HIGHGUID_PLAYER)); - data << uint32(itr->ItemOrMoney); - data << uint32(itr->ItemStackCount); - if (itr->EventType == GUILD_BANK_LOG_MOVE_ITEM || itr->EventType == GUILD_BANK_LOG_MOVE_ITEM2) - data << uint8(itr->DestTabId); // moved tab - data << uint32(time(NULL) - itr->TimeStamp); - } - session->SendPacket(&data); + SendCommandResult(session, GUILD_INVITE_S, ERR_ALREADY_IN_GUILD_S, name); + return; + } + // Invited player cannot be invited + if (pInvitee->GetGuildIdInvited()) + { + SendCommandResult(session, GUILD_INVITE_S, ERR_ALREADY_INVITED_TO_GUILD_S, name); + return; + } + // Inviting player must have rights to invite + if (!_HasRankRight(player, GR_RIGHT_INVITE)) + { + SendCommandResult(session, GUILD_INVITE_S, ERR_GUILD_PERMISSIONS); + return; } - sLog.outDebug("WORLD: Sent (MSG_GUILD_BANK_LOG_QUERY)"); + + sLog.outDebug("Player %s invited %s to join his Guild", player->GetName(), name.c_str()); + + pInvitee->SetGuildIdInvited(m_id); + _LogEvent(GUILD_EVENT_LOG_INVITE_PLAYER, player->GetGUIDLow(), pInvitee->GetGUIDLow()); + + WorldPacket data(SMSG_GUILD_INVITE, 8 + 10); // Guess size + data << player->GetName(); + data << m_name; + pInvitee->GetSession()->SendPacket(&data); + + sLog.outDebug("WORLD: Sent (SMSG_GUILD_INVITE)"); } -void Guild::LogBankEvent(SQLTransaction& trans, uint8 EventType, uint8 TabId, uint32 PlayerGuidLow, uint32 ItemOrMoney, uint16 ItemStackCount, uint8 DestTabId) +void Guild::HandleAcceptMember(WorldSession* session) { - //create Event - GuildBankEventLogEntry NewEvent; - NewEvent.EventType = EventType; - NewEvent.PlayerGuid = PlayerGuidLow; - NewEvent.ItemOrMoney = ItemOrMoney; - NewEvent.ItemStackCount = ItemStackCount; - NewEvent.DestTabId = DestTabId; - NewEvent.TimeStamp = uint32(time(NULL)); + Player* player = session->GetPlayer(); + if (!sWorld.getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GUILD) && + player->GetTeam() != sObjectMgr.GetPlayerTeamByGUID(GetLeaderGUID())) + return; - //add new event to the end of event list - uint32 currentTabId = TabId; - uint32 currentLogGuid = 0; - if (NewEvent.isMoneyEvent()) + if (AddMember(player->GetGUID())) { - m_GuildBankEventLogNextGuid_Money = (m_GuildBankEventLogNextGuid_Money + 1) % sWorld.getIntConfig(CONFIG_GUILD_BANK_EVENT_LOG_COUNT); - currentLogGuid = m_GuildBankEventLogNextGuid_Money; - currentTabId = GUILD_BANK_MONEY_LOGS_TAB; - if (m_GuildBankEventLog_Money.size() >= GUILD_BANK_MAX_LOGS) - m_GuildBankEventLog_Money.pop_front(); + _LogEvent(GUILD_EVENT_LOG_JOIN_GUILD, player->GetGUIDLow()); + _BroadcastEvent(GE_JOINED, player->GetGUID(), player->GetName()); + } +} - m_GuildBankEventLog_Money.push_back(NewEvent); +void Guild::HandleLeaveMember(WorldSession* session) +{ + Player* player = session->GetPlayer(); + // If leader is leaving + if (_IsLeader(player)) + { + if (m_members.size() > 1) + // Leader cannot leave if he is not the last member + SendCommandResult(session, GUILD_QUIT_S, ERR_GUILD_LEADER_LEAVE); + else + // Guild is disbanded if leader leaves. + Disband(); } else { - m_GuildBankEventLogNextGuid_Item[TabId] = ((m_GuildBankEventLogNextGuid_Item[TabId]) + 1) % sWorld.getIntConfig(CONFIG_GUILD_BANK_EVENT_LOG_COUNT); - currentLogGuid = m_GuildBankEventLogNextGuid_Item[TabId]; - if (m_GuildBankEventLog_Item[TabId].size() >= GUILD_BANK_MAX_LOGS) - m_GuildBankEventLog_Item[TabId].pop_front(); - - m_GuildBankEventLog_Item[TabId].push_back(NewEvent); - } + DeleteMember(player->GetGUID(), false, false); - //save event to database - trans->PAppend("DELETE FROM guild_bank_eventlog WHERE guildid='%u' AND LogGuid='%u' AND TabId='%u'", m_Id, currentLogGuid, currentTabId); + _LogEvent(GUILD_EVENT_LOG_LEAVE_GUILD, player->GetGUIDLow()); + _BroadcastEvent(GE_LEFT, player->GetGUID(), player->GetName()); - trans->PAppend("INSERT INTO guild_bank_eventlog (guildid,LogGuid,TabId,EventType,PlayerGuid,ItemOrMoney,ItemStackCount,DestTabId,TimeStamp) VALUES ('%u','%u','%u','%u','%u','%u','%u','%u','" UI64FMTD "')", - m_Id, currentLogGuid, currentTabId, uint32(NewEvent.EventType), NewEvent.PlayerGuid, NewEvent.ItemOrMoney, uint32(NewEvent.ItemStackCount), uint32(NewEvent.DestTabId), NewEvent.TimeStamp); + SendCommandResult(session, GUILD_QUIT_S, ERR_PLAYER_NO_MORE_IN_GUILD, m_name); + } } -bool Guild::AddGBankItemToDB(uint32 GuildId, uint32 BankTab , uint32 BankTabSlot , uint32 GUIDLow, uint32 Entry, SQLTransaction& trans) +void Guild::HandleRemoveMember(WorldSession* session, const std::string& name) { - trans->PAppend("DELETE FROM guild_bank_item WHERE guildid = '%u' AND TabId = '%u'AND SlotId = '%u'", GuildId, BankTab, BankTabSlot); - trans->PAppend("INSERT INTO guild_bank_item (guildid,TabId,SlotId,item_guid,item_entry) " - "VALUES ('%u', '%u', '%u', '%u', '%u')", GuildId, BankTab, BankTabSlot, GUIDLow, Entry); - return true; + Player* player = session->GetPlayer(); + // Player must have rights to remove members + if (!_HasRankRight(player, GR_RIGHT_REMOVE)) + SendCommandResult(session, GUILD_INVITE_S, ERR_GUILD_PERMISSIONS); + // Removed player must be a member of guild + else if (Member* pMember = GetMember(session, name)) + { + // Leader cannot be removed + if (pMember->IsRank(GR_GUILDMASTER)) + SendCommandResult(session, GUILD_QUIT_S, ERR_GUILD_LEADER_LEAVE); + // Do not allow to remove player with the same rank or higher + else if (pMember->IsRankNotLower(player->GetRank())) + SendCommandResult(session, GUILD_QUIT_S, ERR_GUILD_RANK_TOO_HIGH_S, name); + else + { + const uint64& guid = pMember->GetGUID(); + // After call to DeleteMember pointer to member becomes invalid + DeleteMember(guid, false, true); + _LogEvent(GUILD_EVENT_LOG_UNINVITE_PLAYER, player->GetGUIDLow(), GUID_LOPART(guid)); + _BroadcastEvent(GE_REMOVED, 0, name.c_str(), player->GetName()); + } + } } -void Guild::AppendDisplayGuildBankSlot(WorldPacket& data, GuildBankTab const *tab, int slot) +void Guild::HandleUpdateMemberRank(WorldSession* session, const std::string& name, bool demote) { - Item *pItem = tab->Slots[slot]; - uint32 entry = pItem ? pItem->GetEntry() : 0; - - data << uint8(slot); - data << uint32(entry); - if (entry) + Player* player = session->GetPlayer(); + // Player must have rights to promote + if (!_HasRankRight(player, demote ? GR_RIGHT_DEMOTE : GR_RIGHT_PROMOTE)) + SendCommandResult(session, GUILD_INVITE_S, ERR_GUILD_PERMISSIONS); + // Promoted player must be a member of guild + else if (Member* pMember = GetMember(session, name)) { - data << uint32(0); // 3.3.0 (0x8000, 0x8020) - data << uint32(pItem->GetItemRandomPropertyId()); // random item property id + 8 - - if (pItem->GetItemRandomPropertyId()) - data << uint32(pItem->GetItemSuffixFactor()); // SuffixFactor + 4 - - data << uint32(pItem->GetCount()); // +12 ITEM_FIELD_STACK_COUNT - data << uint32(0); // +16 Unknown value - data << uint8(abs(pItem->GetSpellCharges())); // spell charges - - uint8 enchCount = 0; - size_t enchCountPos = data.wpos(); + // Player cannot promote himself + if (pMember->IsSamePlayer(player->GetGUID())) + { + SendCommandResult(session, GUILD_INVITE_S, ERR_GUILD_NAME_INVALID); + return; + } - data << uint8(enchCount); // number of enchantments - for (uint32 i = PERM_ENCHANTMENT_SLOT; i < MAX_ENCHANTMENT_SLOT; ++i) + if (demote) { - if (uint32 enchId = pItem->GetEnchantmentId(EnchantmentSlot(i))) + // Player can demote only lower rank members + if (pMember->IsRankNotLower(player->GetRank())) { - data << uint8(i); - data << uint32(enchId); - ++enchCount; + SendCommandResult(session, GUILD_INVITE_S, ERR_GUILD_RANK_TOO_HIGH_S, name); + return; + } + // Lowest rank cannot be demoted + if (pMember->GetRankId() >= _GetLowestRankId()) + { + SendCommandResult(session, GUILD_INVITE_S, ERR_GUILD_RANK_TOO_LOW_S, name); + return; } } - data.put(enchCountPos, enchCount); + else + { + // Allow to promote only to lower rank than member's rank + // pMember->GetRank() + 1 is the highest rank that current player can promote to + if (pMember->IsRankNotLower(player->GetRank() + 1)) + { + SendCommandResult(session, GUILD_INVITE_S, ERR_GUILD_RANK_TOO_HIGH_S, name); + return; + } + } + + // When promoting player, rank is decreased, when demoting - increased + uint32 newRankId = pMember->GetRankId() + (demote ? 1 : -1); + pMember->ChangeRank(newRankId); + _LogEvent(demote ? GUILD_EVENT_LOG_DEMOTE_PLAYER : GUILD_EVENT_LOG_PROMOTE_PLAYER, player->GetGUIDLow(), GUID_LOPART(pMember->GetGUID()), newRankId); + _BroadcastEvent(demote ? GE_DEMOTION : GE_PROMOTION, 0, player->GetName(), name.c_str(), _GetRankName(newRankId).c_str()); } } -Item* Guild::StoreItem(uint8 tabId, GuildItemPosCountVec const& dest, Item* pItem, SQLTransaction& trans) +void Guild::HandleAddNewRank(WorldSession* session, const std::string& name) { - if (!pItem) - return NULL; + if (_GetRanksSize() >= GUILD_RANKS_MAX_COUNT) + return; + + // Only leader can add new rank + if (!_IsLeader(session->GetPlayer())) + SendCommandResult(session, GUILD_INVITE_S, ERR_GUILD_PERMISSIONS); + else + { + _CreateRank(name, GR_RIGHT_GCHATLISTEN | GR_RIGHT_GCHATSPEAK); + HandleQuery(session); + HandleRoster(); // Broadcast for tab rights update + } +} - Item* lastItem = pItem; +void Guild::HandleRemoveLowestRank(WorldSession* session) +{ + // Cannot remove rank if total count is minimum allowed by the client + if (_GetRanksSize() <= GUILD_RANKS_MIN_COUNT) + return; - for (GuildItemPosCountVec::const_iterator itr = dest.begin(); itr != dest.end();) + // Only leader can delete ranks + if (!_IsLeader(session->GetPlayer())) + SendCommandResult(session, GUILD_INVITE_S, ERR_GUILD_PERMISSIONS); + else { - uint8 slot = itr->Slot; - uint32 count = itr->Count; + uint8 rankId = _GetLowestRankId(); + // Delete bank rights for rank + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_BANK_RIGHTS_FOR_RANK); + stmt->setUInt32(0, m_id); + stmt->setUInt8 (1, rankId); + CharacterDatabase.Execute(stmt); + // Delete rank + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_LOWEST_RANK); + stmt->setUInt32(0, m_id); + stmt->setUInt8 (1, rankId); + CharacterDatabase.Execute(stmt); + + m_ranks.pop_back(); + + HandleQuery(session); + HandleRoster(); // Broadcast for tab rights update + } +} - ++itr; +void Guild::HandleMemberDepositMoney(WorldSession* session, uint32 amount) +{ + if (!_GetPurchasedTabsSize()) + return; // No guild bank tabs - no money in bank - if (itr == dest.end()) - { - lastItem = _StoreItem(tabId, slot, pItem, count, false, trans); - break; - } + Player* player = session->GetPlayer(); - lastItem = _StoreItem(tabId, slot, pItem, count, true, trans); + // Call script after validation and before money transfer. + sScriptMgr.OnGuildMemberDepositMoney(this, player, amount); + + SQLTransaction trans = CharacterDatabase.BeginTransaction(); + // Add money to bank + _ModifyBankMoney(trans, amount, true); + // Remove money from player + player->ModifyMoney(-int32(amount)); + player->SaveGoldToDB(trans); + // Log GM action (TODO: move to scripts) + if (player->GetSession()->GetSecurity() > SEC_PLAYER && sWorld.getBoolConfig(CONFIG_GM_LOG_TRADE)) + { + sLog.outCommand(player->GetSession()->GetAccountId(), + "GM %s (Account: %u) deposit money (Amount: %u) to pGuild bank (Guild ID %u)", + player->GetName(), player->GetSession()->GetAccountId(), amount, m_id); } + // Log guild bank event + _LogBankEvent(trans, GUILD_BANK_LOG_DEPOSIT_MONEY, uint8(0), player->GetGUIDLow(), amount); + + CharacterDatabase.CommitTransaction(trans); - return lastItem; + SendBankTabsInfo(session); + _SendBankContent(session, 0); + _SendBankMoneyUpdate(session); } -// Return stored item (if stored to stack, it can diff. from pItem). And pItem ca be deleted in this case. -Item* Guild::_StoreItem(uint8 tab, uint8 slot, Item *pItem, uint32 count, bool clone, SQLTransaction& trans) +bool Guild::HandleMemberWithdrawMoney(WorldSession* session, uint32 amount, bool repair) { - if (!pItem) - return NULL; + if (!_GetPurchasedTabsSize()) + return false; // No guild bank tabs - no money - sLog.outDebug("GUILD STORAGE: StoreItem tab = %u, slot = %u, item = %u, count = %u", tab, slot, pItem->GetEntry(), count); + if (m_bankMoney < amount) // Not enough money in bank + return false; - Item* pItem2 = m_TabListMap[tab]->Slots[slot]; + Player* player = session->GetPlayer(); + if (!_HasRankRight(player, repair ? GR_RIGHT_WITHDRAW_REPAIR : GR_RIGHT_WITHDRAW_GOLD)) + return false; - if (!pItem2) - { - if (clone) - pItem = pItem->CloneItem(count); - else - pItem->SetCount(count); + uint32 remainingMoney = _GetMemberRemainingMoney(player->GetGUID()); + if (!remainingMoney) + return false; - if (!pItem) - return NULL; + // Call script after validation and before money transfer. + sScriptMgr.OnGuildMemberWitdrawMoney(this, player, amount, repair); - m_TabListMap[tab]->Slots[slot] = pItem; + SQLTransaction trans = CharacterDatabase.BeginTransaction(); + // Update remaining money amount + if (remainingMoney >= amount && remainingMoney < GUILD_WITHDRAW_MONEY_UNLIMITED) + if (Member* pMember = GetMember(player->GetGUID())) + pMember->DecreaseBankRemainingValue(trans, GUILD_BANK_MAX_TABS, amount); + // Remove money from bank + _ModifyBankMoney(trans, amount, false); + // Add money to player (if required) + if (!repair) + { + player->ModifyMoney(amount); + player->SaveGoldToDB(trans); + } + // Log guild bank event + _LogBankEvent(trans, repair ? GUILD_BANK_LOG_REPAIR_MONEY : GUILD_BANK_LOG_WITHDRAW_MONEY, uint8(0), player->GetGUIDLow(), amount); + CharacterDatabase.CommitTransaction(trans); - pItem->SetUInt64Value(ITEM_FIELD_CONTAINED, 0); - pItem->SetUInt64Value(ITEM_FIELD_OWNER, 0); - AddGBankItemToDB(GetId(), tab, slot, pItem->GetGUIDLow(), pItem->GetEntry(), trans); - pItem->FSetState(ITEM_NEW); - pItem->SaveToDB(trans); // not in inventory and can be save standalone + SendMoneyInfo(session); + if (!repair) + { + SendBankTabsInfo(session); + _SendBankContent(session, 0); + _SendBankMoneyUpdate(session); + } + return true; +} - return pItem; +void Guild::HandleMemberLogout(WorldSession* session) +{ + Player* player = session->GetPlayer(); + if (Member* pMember = GetMember(player->GetGUID())) + { + pMember->SetStats(player); + pMember->UpdateLogoutTime(); } + _BroadcastEvent(GE_SIGNED_OFF, player->GetGUID(), player->GetName()); +} + +void Guild::HandleDisband(WorldSession* session) +{ + // Only leader can disband guild + if (!_IsLeader(session->GetPlayer())) + Guild::SendCommandResult(session, GUILD_INVITE_S, ERR_GUILD_PERMISSIONS); else { - pItem2->SetCount(pItem2->GetCount() + count); - pItem2->FSetState(ITEM_CHANGED); - pItem2->SaveToDB(trans); // not in inventory and can be save standalone + Disband(); + sLog.outDebug("WORLD: Guild Successfully Disbanded"); + } +} - if (!clone) - { - pItem->RemoveFromWorld(); - pItem->DeleteFromDB(trans); - delete pItem; - } +/////////////////////////////////////////////////////////////////////////////// +// Send data to client +void Guild::SendInfo(WorldSession* session) const +{ + WorldPacket data(SMSG_GUILD_INFO, m_name.size() + 4 + 4 + 4); + data << m_name; + data << secsToTimeBitFields(m_createdDate); // 3.x (prev. year + month + day) + data << uint32(m_members.size()); // Number of members + data << m_accountsNumber; // Number of accounts + + session->SendPacket(&data); + sLog.outDebug("WORLD: Sent (SMSG_GUILD_INFO)"); +} + +void Guild::SendEventLog(WorldSession *session) const +{ + WorldPacket data(MSG_GUILD_EVENT_LOG_QUERY, 1 + m_eventLog->GetSize() * (1 + 8 + 4)); + m_eventLog->WritePacket(data); + session->SendPacket(&data); + sLog.outDebug("WORLD: Sent (MSG_GUILD_EVENT_LOG_QUERY)"); +} - return pItem2; +void Guild::SendBankLog(WorldSession *session, uint8 tabId) const +{ + // GUILD_BANK_MAX_TABS send by client for money log + if (tabId < _GetPurchasedTabsSize() || tabId == GUILD_BANK_MAX_TABS) + { + const LogHolder* pLog = m_bankEventLog[tabId]; + WorldPacket data(MSG_GUILD_BANK_LOG_QUERY, pLog->GetSize() * (4 * 4 + 1) + 1 + 1); + data << uint8(tabId); + pLog->WritePacket(data); + session->SendPacket(&data); + sLog.outDebug("WORLD: Sent (MSG_GUILD_BANK_LOG_QUERY)"); } } -void Guild::RemoveItem(uint8 tab, uint8 slot, SQLTransaction& trans) +void Guild::SendBankTabData(WorldSession* session, uint8 tabId) const { - m_TabListMap[tab]->Slots[slot] = NULL; - trans->PAppend("DELETE FROM guild_bank_item WHERE guildid='%u' AND TabId='%u' AND SlotId='%u'", - GetId(), uint32(tab), uint32(slot)); + if (tabId < _GetPurchasedTabsSize()) + { + SendMoneyInfo(session); + _SendBankContent(session, tabId); + } } -uint8 Guild::_CanStoreItem_InSpecificSlot(uint8 tab, uint8 slot, GuildItemPosCountVec &dest, uint32& count, bool swap, Item* pSrcItem) const +void Guild::SendBankTabsInfo(WorldSession *session) const { - Item* pItem2 = m_TabListMap[tab]->Slots[slot]; + WorldPacket data(SMSG_GUILD_BANK_LIST, 500); - // ignore move item (this slot will be empty at move) - if (pItem2 == pSrcItem) - pItem2 = NULL; + data << uint64(m_bankMoney); + data << uint8(0); // TabInfo packet must be for tabId 0 + data << uint32(_GetMemberRemainingSlots(session->GetPlayer()->GetGUID(), 0)); + data << uint8(1); // Tell client that this packet includes tab info - uint32 need_space; + data << uint8(_GetPurchasedTabsSize()); // Number of tabs + for (uint8 i = 0; i < _GetPurchasedTabsSize(); ++i) + m_bankTabs[i]->WriteInfoPacket(data); - // empty specific slot - check item fit to slot - if (!pItem2 || swap) - { - // non empty stack with space - need_space = pSrcItem->GetMaxStackCount(); - } - // non empty slot, check item type - else - { - // check item type - if (pItem2->GetEntry() != pSrcItem->GetEntry()) - return EQUIP_ERR_ITEM_CANT_STACK; + data << uint8(0); // Do not send tab content + session->SendPacket(&data); - // check free space - if (pItem2->GetCount() >= pSrcItem->GetMaxStackCount()) - return EQUIP_ERR_ITEM_CANT_STACK; + sLog.outDebug("WORLD: Sent (SMSG_GUILD_BANK_LIST)"); +} - need_space = pSrcItem->GetMaxStackCount() - pItem2->GetCount(); - } +void Guild::SendBankTabText(WorldSession *session, uint8 tabId) const +{ + if (const BankTab* pTab = GetBankTab(tabId)) + pTab->SendText(this, session); +} - if (need_space > count) - need_space = count; +void Guild::SendPermissions(WorldSession *session) const +{ + const uint64& guid = session->GetPlayer()->GetGUID(); + uint8 rankId = session->GetPlayer()->GetRank(); - GuildItemPosCount newPosition = GuildItemPosCount(slot,need_space); - if (!newPosition.isContainedIn(dest)) + WorldPacket data(MSG_GUILD_PERMISSIONS, 4 * 15 + 1); + data << uint32(rankId); + data << uint32(_GetRankRights(rankId)); + data << uint32(_GetMemberRemainingMoney(guid)); + data << uint8 (_GetPurchasedTabsSize()); + // Why sending all info when not all tabs are purchased??? + for (uint8 tabId = 0; tabId < GUILD_BANK_MAX_TABS; ++tabId) { - dest.push_back(newPosition); - count -= need_space; + data << uint32(_GetRankBankTabRights(rankId, tabId)); + data << uint32(_GetMemberRemainingSlots(guid, tabId)); } + session->SendPacket(&data); + sLog.outDebug("WORLD: Sent (MSG_GUILD_PERMISSIONS)"); +} - return EQUIP_ERR_OK; +void Guild::SendMoneyInfo(WorldSession *session) const +{ + WorldPacket data(MSG_GUILD_BANK_MONEY_WITHDRAWN, 4); + data << uint32(_GetMemberRemainingMoney(session->GetPlayer()->GetGUID())); + session->SendPacket(&data); + sLog.outDebug("WORLD: Sent MSG_GUILD_BANK_MONEY_WITHDRAWN"); } -uint8 Guild::_CanStoreItem_InTab(uint8 tab, GuildItemPosCountVec &dest, uint32& count, bool merge, Item* pSrcItem, uint8 skip_slot) const +void Guild::SendLoginInfo(WorldSession* session) const { - ASSERT(pSrcItem); - for (uint32 j = 0; j < GUILD_BANK_MAX_SLOTS; ++j) - { - // skip specific slot already processed in first called _CanStoreItem_InSpecificSlot - if (j == skip_slot) - continue; + WorldPacket data(SMSG_GUILD_EVENT, 1 + 1 + m_motd.size() + 1); + data << uint8(GE_MOTD); + data << uint8(1); + data << m_motd; + session->SendPacket(&data); + sLog.outDebug("WORLD: Sent guild MOTD (SMSG_GUILD_EVENT)"); - Item* pItem2 = m_TabListMap[tab]->Slots[j]; + SendBankTabsInfo(session); - // ignore move item (this slot will be empty at move) - if (pItem2 == pSrcItem) - pItem2 = NULL; + _BroadcastEvent(GE_SIGNED_ON, session->GetPlayer()->GetGUID(), session->GetPlayer()->GetName()); +} - // if merge skip empty, if !merge skip non-empty - if ((pItem2 != NULL) != merge) - continue; +/////////////////////////////////////////////////////////////////////////////// +// Loading methods +bool Guild::LoadFromDB(Field* fields) +{ + m_id = fields[0].GetUInt32(); + m_name = fields[1].GetString(); + m_leaderGuid = MAKE_NEW_GUID(fields[2].GetUInt32(), 0, HIGHGUID_PLAYER); + m_emblemInfo.LoadFromDB(fields); + m_info = fields[8].GetString(); + m_motd = fields[9].GetString(); + m_createdDate = fields[10].GetUInt64(); + m_bankMoney = fields[11].GetUInt64(); + + uint8 purchasedTabs = uint8(fields[12].GetUInt32()); + if (purchasedTabs > GUILD_BANK_MAX_TABS) + purchasedTabs = GUILD_BANK_MAX_TABS; + + m_bankTabs.resize(purchasedTabs); + for (uint8 i = 0; i < purchasedTabs; ++i) + m_bankTabs[i] = new BankTab(m_id, i); + + _CreateLogHolders(); + return true; +} + +bool Guild::LoadRankFromDB(Field* fields) +{ + RankInfo rankInfo(m_id); + if (!rankInfo.LoadFromDB(fields)) + return false; + m_ranks.push_back(rankInfo); + return true; +} + +bool Guild::LoadMemberFromDB(Field* fields) +{ + uint32 lowguid = fields[1].GetUInt32(); + Member *pMember = new Member(m_id, MAKE_NEW_GUID(lowguid, 0, HIGHGUID_PLAYER), fields[2].GetUInt8()); + if (!pMember->LoadFromDB(fields)) + { + _DeleteMemberFromDB(lowguid); + delete pMember; + return false; + } + m_members[lowguid] = pMember; + return true; +} + +bool Guild::LoadBankRightFromDB(Field* fields) +{ + // rights slots + GuildBankRightsAndSlots rightsAndSlots(fields[3].GetUInt8(), fields[4].GetUInt32()); + // rankId tabId + _SetRankBankTabRightsAndSlots(fields[2].GetUInt8(), fields[1].GetUInt8(), rightsAndSlots, false); + return true; +} + +bool Guild::LoadEventLogFromDB(Field* fields) +{ + if (m_eventLog->CanInsert()) + { + m_eventLog->LoadEvent(new EventLogEntry( + m_id, // guild id + fields[1].GetUInt32(), // guid + fields[6].GetUInt64(), // timestamp + GuildEventLogTypes(fields[2].GetUInt8()), // event type + fields[3].GetUInt32(), // player guid 1 + fields[4].GetUInt32(), // player guid 2 + fields[5].GetUInt8())); // rank + return true; + } + return false; +} - if (pItem2) +bool Guild::LoadBankEventLogFromDB(Field* fields) +{ + uint8 dbTabId = fields[1].GetUInt8(); + bool isMoneyTab = (dbTabId == GUILD_BANK_MONEY_LOGS_TAB); + if (dbTabId < _GetPurchasedTabsSize() || isMoneyTab) + { + uint8 tabId = isMoneyTab ? uint8(GUILD_BANK_MAX_TABS) : dbTabId; + LogHolder* pLog = m_bankEventLog[tabId]; + if (pLog->CanInsert()) { - if (pItem2->GetEntry() == pSrcItem->GetEntry() && pItem2->GetCount() < pSrcItem->GetMaxStackCount()) + uint32 guid = fields[2].GetUInt32(); + GuildBankEventLogTypes eventType = GuildBankEventLogTypes(fields[3].GetUInt8()); + if (BankEventLogEntry::IsMoneyEvent(eventType)) { - uint32 need_space = pSrcItem->GetMaxStackCount() - pItem2->GetCount(); - if (need_space > count) - need_space = count; - - GuildItemPosCount newPosition = GuildItemPosCount(j, need_space); - if (!newPosition.isContainedIn(dest)) + if (!isMoneyTab) { - dest.push_back(newPosition); - count -= need_space; - - if (count == 0) - return EQUIP_ERR_OK; + sLog.outError("GuildBankEventLog ERROR: MoneyEvent(LogGuid: %u, Guild: %u) does not belong to money tab (%u), ignoring...", guid, m_id, dbTabId); + return false; } } - } - else - { - uint32 need_space = pSrcItem->GetMaxStackCount(); - if (need_space > count) - need_space = count; - - GuildItemPosCount newPosition = GuildItemPosCount(j, need_space); - if (!newPosition.isContainedIn(dest)) + else if (isMoneyTab) { - dest.push_back(newPosition); - count -= need_space; - - if (count == 0) - return EQUIP_ERR_OK; + sLog.outError("GuildBankEventLog ERROR: non-money event (LogGuid: %u, Guild: %u) belongs to money tab, ignoring...", guid, m_id); + return false; } + pLog->LoadEvent(new BankEventLogEntry( + m_id, // guild id + guid, // guid + fields[8].GetUInt64(), // timestamp + dbTabId, // tab id + eventType, // event type + fields[4].GetUInt32(), // player guid + fields[5].GetUInt32(), // item or money + fields[6].GetUInt16(), // itam stack count + fields[7].GetUInt8())); // dest tab id } } - return EQUIP_ERR_OK; + return true; } -uint8 Guild::CanStoreItem(uint8 tab, uint8 slot, GuildItemPosCountVec &dest, uint32 count, Item *pItem, bool swap) const +bool Guild::LoadBankTabFromDB(Field* fields) { - sLog.outDebug("GUILD STORAGE: CanStoreItem tab = %u, slot = %u, item = %u, count = %u", tab, slot, pItem->GetEntry(), count); + uint32 tabId = fields[1].GetUInt8(); + if (tabId >= _GetPurchasedTabsSize()) + { + sLog.outError("Invalid tab (tabId: %u) in guild bank, skipped.", tabId); + return false; + } + return m_bankTabs[tabId]->LoadFromDB(fields); +} - if (count > pItem->GetCount()) - return EQUIP_ERR_COULDNT_SPLIT_ITEMS; +bool Guild::LoadBankItemFromDB(Field* fields) +{ + uint8 tabId = fields[12].GetUInt8(); + if (tabId >= _GetPurchasedTabsSize()) + { + sLog.outError("Invalid tab for item (GUID: %u, id: #%u) in guild bank, skipped.", + fields[14].GetUInt32(), fields[15].GetUInt32()); + return false; + } + return m_bankTabs[tabId]->LoadItemFromDB(fields); +} - if (pItem->IsSoulBound()) - return EQUIP_ERR_CANT_DROP_SOULBOUND; +// Validates guild data loaded from database. Returns false if guild should be deleted. +bool Guild::Validate() +{ + // Validate ranks data + // GUILD RANKS represent a sequence starting from 0 = GUILD_MASTER (ALL PRIVILEGES) to max 9 (lowest privileges). + // The lower rank id is considered higher rank - so promotion does rank-- and demotion does rank++ + // Between ranks in sequence cannot be gaps - so 0,1,2,4 is impossible + // Min ranks count is 5 and max is 10. + bool broken_ranks = false; + if (_GetRanksSize() < GUILD_RANKS_MIN_COUNT || _GetRanksSize() > GUILD_RANKS_MAX_COUNT) + { + sLog.outError("Guild %u has invalid number of ranks, creating new...", m_id); + broken_ranks = true; + } + else + { + for (uint8 rankId = 0; rankId < _GetRanksSize(); ++rankId) + { + RankInfo* rankInfo = GetRankInfo(rankId); + if (rankInfo->GetId() != rankId) + { + sLog.outError("Guild %u has broken rank id %u, creating default set of ranks...", m_id, rankId); + broken_ranks = true; + } + } + } + if (broken_ranks) + { + m_ranks.clear(); + _CreateDefaultGuildRanks(DEFAULT_LOCALE); + } - // in specific tab - if (tab >= m_TabListMap.size() || tab >= GUILD_BANK_MAX_TABS) - return EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG; + // Validate members' data + for (Members::iterator itr = m_members.begin(); itr != m_members.end(); ++itr) + if (itr->second->GetRankId() > _GetRanksSize()) + itr->second->ChangeRank(_GetLowestRankId()); - // in specific slot - if (slot != NULL_SLOT) + // Repair the structure of the guild. + // If the guildmaster doesn't exist or isn't member of the guild + // attempt to promote another member. + Member* pLeader = GetMember(m_leaderGuid); + if (!pLeader) { - uint8 res = _CanStoreItem_InSpecificSlot(tab,slot,dest,count,swap,pItem); - if (res != EQUIP_ERR_OK) - return res; + DeleteMember(m_leaderGuid); + // If no more members left, disband guild + if (m_members.empty()) + { + Disband(); + return false; + } + } + else if (!pLeader->IsRank(GR_GUILDMASTER)) + _SetLeaderGUID(pLeader); - if (count == 0) - return EQUIP_ERR_OK; + // Check config if multiple guildmasters are allowed + if (!sConfig.GetBoolDefault("Guild.AllowMultipleGuildMaster", 0)) + for (Members::iterator itr = m_members.begin(); itr != m_members.end(); ++itr) + if (itr->second->GetRankId() == GR_GUILDMASTER && !itr->second->IsSamePlayer(m_leaderGuid)) + itr->second->ChangeRank(GR_OFFICER); + + _UpdateAccountsNumber(); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// Broadcasts +void Guild::BroadcastToGuild(WorldSession *session, bool officerOnly, const std::string& msg, uint32 language) const +{ + if (session && session->GetPlayer() && _HasRankRight(session->GetPlayer(), officerOnly ? GR_RIGHT_OFFCHATSPEAK : GR_RIGHT_GCHATSPEAK)) + { + WorldPacket data; + ChatHandler::FillMessageData(&data, session, officerOnly ? CHAT_MSG_OFFICER : CHAT_MSG_GUILD, language, NULL, 0, msg.c_str(), NULL); + for (Members::const_iterator itr = m_members.begin(); itr != m_members.end(); ++itr) + if (Player *player = itr->second->FindPlayer()) + if (player->GetSession() && _HasRankRight(player, officerOnly ? GR_RIGHT_OFFCHATLISTEN : GR_RIGHT_GCHATLISTEN) && + !player->GetSocial()->HasIgnore(session->GetPlayer()->GetGUIDLow())) + player->GetSession()->SendPacket(&data); } +} + +void Guild::BroadcastPacketToRank(WorldPacket *packet, uint8 rankId) const +{ + for (Members::const_iterator itr = m_members.begin(); itr != m_members.end(); ++itr) + if (itr->second->IsRank(rankId)) + if (Player *player = itr->second->FindPlayer()) + player->GetSession()->SendPacket(packet); +} - // not specific slot or have space for partly store only in specific slot +void Guild::BroadcastPacket(WorldPacket *packet) const +{ + for (Members::const_iterator itr = m_members.begin(); itr != m_members.end(); ++itr) + if (Player *player = itr->second->FindPlayer()) + player->GetSession()->SendPacket(packet); +} - // search stack in tab for merge to - if (pItem->GetMaxStackCount() > 1) +/////////////////////////////////////////////////////////////////////////////// +// Members handling +bool Guild::AddMember(const uint64& guid, uint8 rankId) +{ + Player* player = sObjectMgr.GetPlayer(guid); + // Player cannot be in guild + if (player) { - uint8 res = _CanStoreItem_InTab(tab, dest, count, true, pItem, slot); - if (res != EQUIP_ERR_OK) - return res; + if (player->GetGuildId() != 0) + return false; + } + else if (Player::GetGuildIdFromDB(guid) != 0) + return false; - if (count == 0) - return EQUIP_ERR_OK; + // Remove all player signs from another petitions + // This will be prevent attempt to join many guilds and corrupt guild data integrity + Player::RemovePetitionsAndSigns(guid, GUILD_CHARTER_TYPE); + + uint32 lowguid = GUID_LOPART(guid); + + // If rank was not passed, assing lowest possible rank + if (rankId == GUILD_RANK_NONE) + rankId = _GetLowestRankId(); + + Member* pMember = new Member(m_id, guid, rankId); + if (player) + pMember->SetStats(player); + else + { + bool ok = false; + // Player must exist + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_LOAD_CHAR_DATA_FOR_GUILD); + stmt->setUInt32(0, lowguid); + if (PreparedQueryResult result = CharacterDatabase.Query(stmt)) + { + Field *fields = result->Fetch(); + pMember->SetStats( + fields[0].GetString(), + fields[1].GetUInt8(), + fields[2].GetUInt8(), + fields[3].GetUInt32(), + fields[4].GetUInt32()); + + ok = pMember->CheckStats(); + } + if (!ok) + { + delete pMember; + return false; + } } + m_members[lowguid] = pMember; - // search free slot in bag for place to - uint8 res = _CanStoreItem_InTab(tab, dest, count, false, pItem, slot); - if (res != EQUIP_ERR_OK) - return res; + SQLTransaction trans(NULL); + pMember->SaveToDB(trans); + // If player not in game data in will be loaded from guild tables, so no need to update it! + if (player) + { + player->SetInGuild(m_id); + player->SetRank(rankId); + player->SetGuildIdInvited(0); + } - if (count == 0) - return EQUIP_ERR_OK; + _UpdateAccountsNumber(); - return EQUIP_ERR_BANK_FULL; + // Call scripts if member was succesfully added (and stored to database) + sScriptMgr.OnGuildAddMember(this, player, rankId); + + return true; } -void Guild::SetGuildBankTabText(uint8 TabId, std::string text) +void Guild::DeleteMember(const uint64& guid, bool isDisbanding, bool isKicked) { - if (TabId >= GetPurchasedTabs()) - return; + uint32 lowguid = GUID_LOPART(guid); + Player *player = sObjectMgr.GetPlayer(guid); - if (!m_TabListMap[TabId]) - return; + // Guild master can be deleted when loading guild and guid doesn't exist in characters table + // or when he is removed from guild by gm command + if (m_leaderGuid == guid && !isDisbanding) + { + Member* oldLeader = NULL; + Member* newLeader = NULL; + for (Guild::Members::iterator i = m_members.begin(); i != m_members.end(); ++i) + { + if (i->first == lowguid) + oldLeader = i->second; + else if (!newLeader || newLeader->GetRankId() > i->second->GetRankId()) + newLeader = i->second; + } + if (!newLeader) + { + Disband(); + return; + } - if (m_TabListMap[TabId]->Text == text) - return; + _SetLeaderGUID(newLeader); - utf8truncate(text, 500); // DB and client size limitation + // If player not online data in data field will be loaded from guild tabs no need to update it !! + if (Player *newLeaderPlayer = newLeader->FindPlayer()) + newLeaderPlayer->SetRank(GR_GUILDMASTER); - m_TabListMap[TabId]->Text = text; + // If leader does not exist (at guild loading with deleted leader) do not send broadcasts + if (oldLeader) + { + _BroadcastEvent(GE_LEADER_CHANGED, 0, oldLeader->GetName().c_str(), newLeader->GetName().c_str()); + _BroadcastEvent(GE_LEFT, guid, oldLeader->GetName().c_str()); + } + } + // Call script on remove before member is acutally removed from guild (and database) + sScriptMgr.OnGuildRemoveMember(this, player, isDisbanding, isKicked); - CharacterDatabase.escape_string(text); - CharacterDatabase.PExecute("UPDATE guild_bank_tab SET TabText='%s' WHERE guildid='%u' AND TabId='%u'", text.c_str(), m_Id, uint32(TabId)); + if (Member* pMember = GetMember(guid)) + delete pMember; + m_members.erase(lowguid); + + // If player not online data in data field will be loaded from guild tabs no need to update it !! + if (player) + { + player->SetInGuild(0); + player->SetRank(0); + } - // announce - SendGuildBankTabText(NULL,TabId); + _DeleteMemberFromDB(lowguid); + if (!isDisbanding) + _UpdateAccountsNumber(); +} + +bool Guild::ChangeMemberRank(const uint64& guid, uint8 newRank) +{ + if (newRank <= _GetLowestRankId()) // Validate rank (allow only existing ranks) + if (Member* pMember = GetMember(guid)) + { + pMember->ChangeRank(newRank); + return true; + } + return false; } -void Guild::SendGuildBankTabText(WorldSession *session, uint8 TabId) +/////////////////////////////////////////////////////////////////////////////// +// Bank (items move) +void Guild::SwapItems(Player* player, uint8 tabId, uint8 slotId, uint8 destTabId, uint8 destSlotId, uint32 splitedAmount) { - GuildBankTab const* tab = m_TabListMap[TabId]; + if (tabId >= _GetPurchasedTabsSize() || slotId >= GUILD_BANK_MAX_SLOTS || + destTabId >= _GetPurchasedTabsSize() || destSlotId >= GUILD_BANK_MAX_SLOTS) + return; - WorldPacket data(MSG_QUERY_GUILD_BANK_TEXT, 1+tab->Text.size()+1); - data << uint8(TabId); - data << tab->Text; + if (tabId == destTabId && slotId == destSlotId) + return; - if (session) - session->SendPacket(&data); - else - BroadcastPacket(&data); + BankMoveItemData from(this, player, tabId, slotId); + BankMoveItemData to(this, player, destTabId, destSlotId); + _MoveItems(&from, &to, splitedAmount); } -void Guild::SwapItems(Player * pl, uint8 BankTab, uint8 BankTabSlot, uint8 BankTabDst, uint8 BankTabSlotDst, uint32 SplitedAmount) +void Guild::SwapItemsWithInventory(Player* player, bool toChar, uint8 tabId, uint8 slotId, uint8 playerBag, uint8 playerSlotId, uint32 splitedAmount) { - // empty operation - if (BankTab == BankTabDst && BankTabSlot == BankTabSlotDst) + if ((slotId >= GUILD_BANK_MAX_SLOTS && slotId != NULL_SLOT) || tabId >= _GetPurchasedTabsSize()) return; - Item *pItemSrc = GetItem(BankTab, BankTabSlot); - if (!pItemSrc) // may prevent crash - return; + BankMoveItemData bankData(this, player, tabId, slotId); + PlayerMoveItemData charData(this, player, playerBag, playerSlotId); + if (toChar) + _MoveItems(&bankData, &charData, splitedAmount); + else + _MoveItems(&charData, &bankData, splitedAmount); +} - if (pItemSrc->GetCount() == 0) +/////////////////////////////////////////////////////////////////////////////// +// Bank tabs +void Guild::SetBankTabText(uint8 tabId, const std::string& text) +{ + if (BankTab* pTab = GetBankTab(tabId)) { - sLog.outCrash("Guild::SwapItems: Player %s(GUIDLow: %u) tried to move item %u from tab %u slot %u to tab %u slot %u, but item %u has a stack of zero!", - pl->GetName(), pl->GetGUIDLow(), pItemSrc->GetEntry(), BankTab, BankTabSlot, BankTabDst, BankTabSlotDst, pItemSrc->GetEntry()); - //return; // Commented out for now, uncomment when it's verified that this causes a crash!! + pTab->SetText(text); + pTab->SendText(this, NULL); } +} - if (SplitedAmount >= pItemSrc->GetCount()) - SplitedAmount = 0; // no split +/////////////////////////////////////////////////////////////////////////////// +// Private methods +void Guild::_CreateLogHolders() +{ + m_eventLog = new LogHolder(m_id, sWorld.getIntConfig(CONFIG_GUILD_EVENT_LOG_COUNT)); + for (uint8 tabId = 0; tabId <= GUILD_BANK_MAX_TABS; ++tabId) + m_bankEventLog[tabId] = new LogHolder(m_id, sWorld.getIntConfig(CONFIG_GUILD_BANK_EVENT_LOG_COUNT)); +} - Item *pItemDst = GetItem(BankTabDst, BankTabSlotDst); +bool Guild::_CreateNewBankTab() +{ + if (_GetPurchasedTabsSize() >= GUILD_BANK_MAX_TABS) + return false; - if (BankTab != BankTabDst) - { - // check dest pos rights (if different tabs) - if (!IsMemberHaveRights(pl->GetGUIDLow(), BankTabDst, GUILD_BANK_RIGHT_DEPOSIT_ITEM)) - return; + uint8 tabId = _GetPurchasedTabsSize(); // Next free id + m_bankTabs.push_back(new BankTab(m_id, tabId)); - // check source pos rights (if different tabs) - uint32 remRight = GetMemberSlotWithdrawRem(pl->GetGUIDLow(), BankTab); - if (remRight <= 0) - return; - } + PreparedStatement* stmt = NULL; + SQLTransaction trans = CharacterDatabase.BeginTransaction(); - if (SplitedAmount) - { // Bank -> Bank item split (in empty or non empty slot - GuildItemPosCountVec dest; - uint8 msg = CanStoreItem(BankTabDst, BankTabSlotDst, dest, SplitedAmount, pItemSrc, false); - if (msg != EQUIP_ERR_OK) - { - pl->SendEquipError(msg, pItemSrc, NULL); - return; - } + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_BANK_TAB); + stmt->setUInt32(0, m_id); + stmt->setUInt8 (1, tabId); + trans->Append(stmt); - Item *pNewItem = pItemSrc->CloneItem(SplitedAmount); - if (!pNewItem) - { - pl->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, pItemSrc, NULL); - return; - } + stmt = CharacterDatabase.GetPreparedStatement(CHAR_ADD_GUILD_BANK_TAB); + stmt->setUInt32(0, m_id); + stmt->setUInt8 (1, tabId); + trans->Append(stmt); - SQLTransaction trans = CharacterDatabase.BeginTransaction(); - LogBankEvent(trans, GUILD_BANK_LOG_MOVE_ITEM, BankTab, pl->GetGUIDLow(), pItemSrc->GetEntry(), SplitedAmount, BankTabDst); + CharacterDatabase.CommitTransaction(trans); + return true; +} - pl->ItemRemovedQuestCheck(pItemSrc->GetEntry(), SplitedAmount); - pItemSrc->SetCount(pItemSrc->GetCount() - SplitedAmount); - pItemSrc->FSetState(ITEM_CHANGED); - pItemSrc->SaveToDB(trans); // not in inventory and can be save standalone - StoreItem(BankTabDst, dest, pNewItem, trans); - CharacterDatabase.CommitTransaction(trans); - } - else // non split - { - GuildItemPosCountVec gDest; - uint8 msg = CanStoreItem(BankTabDst, BankTabSlotDst, gDest, pItemSrc->GetCount(), pItemSrc, false); - if (msg == EQUIP_ERR_OK) // merge to - { - SQLTransaction trans = CharacterDatabase.BeginTransaction(); - LogBankEvent(trans, GUILD_BANK_LOG_MOVE_ITEM, BankTab, pl->GetGUIDLow(), pItemSrc->GetEntry(), pItemSrc->GetCount(), BankTabDst); +void Guild::_CreateDefaultGuildRanks(LocaleConstant loc) +{ + PreparedStatement* stmt = NULL; - RemoveItem(BankTab, BankTabSlot, trans); - StoreItem(BankTabDst, gDest, pItemSrc, trans); - CharacterDatabase.CommitTransaction(trans); - } - else // swap - { - gDest.clear(); - msg = CanStoreItem(BankTabDst, BankTabSlotDst, gDest, pItemSrc->GetCount(), pItemSrc, true); - if (msg != EQUIP_ERR_OK) - { - pl->SendEquipError(msg, pItemSrc, NULL); - return; - } + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_RANKS); + stmt->setUInt32(0, m_id); + CharacterDatabase.Execute(stmt); - GuildItemPosCountVec gSrc; - msg = CanStoreItem(BankTab, BankTabSlot, gSrc, pItemDst->GetCount(), pItemDst, true); - if (msg != EQUIP_ERR_OK) - { - pl->SendEquipError(msg, pItemDst, NULL); - return; - } + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GUILD_BANK_RIGHTS); + stmt->setUInt32(0, m_id); + CharacterDatabase.Execute(stmt); - if (BankTab != BankTabDst) - { - // check source pos rights (item swapped to src) - if (!IsMemberHaveRights(pl->GetGUIDLow(), BankTab, GUILD_BANK_RIGHT_DEPOSIT_ITEM)) - return; - - // check dest pos rights (item swapped to src) - uint32 remRightDst = GetMemberSlotWithdrawRem(pl->GetGUIDLow(), BankTabDst); - if (remRightDst <= 0) - return; - } + _CreateRank(sObjectMgr.GetTrinityString(LANG_GUILD_MASTER, loc), GR_RIGHT_ALL); + _CreateRank(sObjectMgr.GetTrinityString(LANG_GUILD_OFFICER, loc), GR_RIGHT_ALL); + _CreateRank(sObjectMgr.GetTrinityString(LANG_GUILD_VETERAN, loc), GR_RIGHT_GCHATLISTEN | GR_RIGHT_GCHATSPEAK); + _CreateRank(sObjectMgr.GetTrinityString(LANG_GUILD_MEMBER, loc), GR_RIGHT_GCHATLISTEN | GR_RIGHT_GCHATSPEAK); + _CreateRank(sObjectMgr.GetTrinityString(LANG_GUILD_INITIATE, loc), GR_RIGHT_GCHATLISTEN | GR_RIGHT_GCHATSPEAK); +} - SQLTransaction trans = CharacterDatabase.BeginTransaction(); - LogBankEvent(trans, GUILD_BANK_LOG_MOVE_ITEM, BankTab, pl->GetGUIDLow(), pItemSrc->GetEntry(), pItemSrc->GetCount(), BankTabDst); - LogBankEvent(trans, GUILD_BANK_LOG_MOVE_ITEM, BankTabDst, pl->GetGUIDLow(), pItemDst->GetEntry(), pItemDst->GetCount(), BankTab); +void Guild::_CreateRank(const std::string& name, uint32 rights) +{ + if (_GetRanksSize() >= GUILD_RANKS_MAX_COUNT) + return; - RemoveItem(BankTab, BankTabSlot, trans); - RemoveItem(BankTabDst, BankTabSlotDst, trans); - StoreItem(BankTab, gSrc, pItemDst, trans); - StoreItem(BankTabDst, gDest, pItemSrc, trans); - CharacterDatabase.CommitTransaction(trans); - } + // Ranks represent sequence 0,1,2,... where 0 means guildmaster + uint8 newRankId = _GetRanksSize(); + + RankInfo info(m_id, newRankId, name, rights, 0); + m_ranks.push_back(info); + + SQLTransaction trans = CharacterDatabase.BeginTransaction(); + for (uint8 i = 0; i < _GetPurchasedTabsSize(); ++i) + { + // Create bank rights with default values + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_ADD_GUILD_BANK_RIGHT_DEFAULT); + stmt->setUInt32(0, m_id); + stmt->setUInt8 (1, i); + stmt->setUInt8 (2, newRankId); + trans->Append(stmt); } - DisplayGuildBankContentUpdate(BankTab,BankTabSlot,BankTab == BankTabDst ? BankTabSlotDst : -1); - if (BankTab != BankTabDst) - DisplayGuildBankContentUpdate(BankTabDst,BankTabSlotDst); + info.SaveToDB(trans); + CharacterDatabase.CommitTransaction(trans); } -void Guild::MoveFromBankToChar(Player * pl, uint8 BankTab, uint8 BankTabSlot, uint8 PlayerBag, uint8 PlayerSlot, uint32 SplitedAmount) +// Updates the number of accounts that are in the guild +// Player may have many characters in the guild, but with the same account +void Guild::_UpdateAccountsNumber() { - Item *pItemBank = GetItem(BankTab, BankTabSlot); - Item *pItemChar = pl->GetItemByPos(PlayerBag, PlayerSlot); + // We use a set to be sure each element will be unique + std::set accountsIdSet; + for (Members::const_iterator itr = m_members.begin(); itr != m_members.end(); ++itr) + accountsIdSet.insert(itr->second->GetAccountId()); - if (!pItemBank) // Problem to get bank item - return; + m_accountsNumber = accountsIdSet.size(); +} - if (SplitedAmount > pItemBank->GetCount()) - return; // cheating? - else if (SplitedAmount == pItemBank->GetCount()) - SplitedAmount = 0; // no split +// Detects if player is the guild master. +// Check both leader guid and player's rank (otherwise multiple feature with +// multiple guild masters won't work) +bool Guild::_IsLeader(Player* player) const +{ + if (player->GetGUID() == m_leaderGuid) + return true; + if (const Member* pMember = GetMember(player->GetGUID())) + return pMember->IsRank(GR_GUILDMASTER); + return false; +} - if (SplitedAmount) - { // Bank -> Char split to slot (patly move) - Item *pNewItem = pItemBank->CloneItem(SplitedAmount); - if (!pNewItem) - { - pl->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, pItemBank, NULL); - return; - } +void Guild::_DeleteBankItems(SQLTransaction& trans, bool removeItemsFromDB) +{ + for (uint8 tabId = 0; tabId < _GetPurchasedTabsSize(); ++tabId) + { + m_bankTabs[tabId]->Delete(trans, removeItemsFromDB); + delete m_bankTabs[tabId]; + m_bankTabs[tabId] = NULL; + } + m_bankTabs.clear(); +} - ItemPosCountVec dest; - uint8 msg = pl->CanStoreItem(PlayerBag, PlayerSlot, dest, pNewItem, false); - if (msg != EQUIP_ERR_OK) - { - pl->SendEquipError(msg, pNewItem, NULL); - delete pNewItem; - return; - } +bool Guild::_ModifyBankMoney(SQLTransaction& trans, const uint64& amount, bool add) +{ + if (add) + m_bankMoney += amount; + else + { + // Check if there is enough money in bank. + if (m_bankMoney < amount) + return false; + m_bankMoney -= amount; + } - // check source pos rights (item moved to inventory) - uint32 remRight = GetMemberSlotWithdrawRem(pl->GetGUIDLow(), BankTab); - if (remRight <= 0) - { - delete pNewItem; - return; - } + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SET_GUILD_BANK_MONEY); + stmt->setUInt64(0, m_bankMoney); + stmt->setUInt32(1, m_id); + trans->Append(stmt); + return true; +} - SQLTransaction trans = CharacterDatabase.BeginTransaction(); - LogBankEvent(trans, GUILD_BANK_LOG_WITHDRAW_ITEM, BankTab, pl->GetGUIDLow(), pItemBank->GetEntry(), SplitedAmount); +void Guild::_SetLeaderGUID(Member* pLeader) +{ + if (!pLeader) + return; - pItemBank->SetCount(pItemBank->GetCount()-SplitedAmount); - pItemBank->FSetState(ITEM_CHANGED); - pItemBank->SaveToDB(trans); // not in inventory and can be save standalone - pl->MoveItemToInventory(dest, pNewItem, true); - pl->SaveInventoryAndGoldToDB(trans); + m_leaderGuid = pLeader->GetGUID(); + pLeader->ChangeRank(GR_GUILDMASTER); - MemberItemWithdraw(BankTab, pl->GetGUIDLow(), trans); - CharacterDatabase.CommitTransaction(trans); - } - else // Bank -> Char swap with slot (move) + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SET_GUILD_LEADER); + stmt->setUInt32(0, GUID_LOPART(m_leaderGuid)); + stmt->setUInt32(1, m_id); + CharacterDatabase.Execute(stmt); +} + +void Guild::_SetRankBankMoneyPerDay(uint8 rankId, uint32 moneyPerDay) +{ + if (RankInfo* rankInfo = GetRankInfo(rankId)) { - ItemPosCountVec dest; - uint8 msg = pl->CanStoreItem(PlayerBag, PlayerSlot, dest, pItemBank, false); - if (msg == EQUIP_ERR_OK) // merge case - { - // check source pos rights (item moved to inventory) - uint32 remRight = GetMemberSlotWithdrawRem(pl->GetGUIDLow(), BankTab); - if (remRight <= 0) - return; + for (Members::iterator itr = m_members.begin(); itr != m_members.end(); ++itr) + if (itr->second->IsRank(rankId)) + itr->second->ResetMoneyTime(); - SQLTransaction trans = CharacterDatabase.BeginTransaction(); - LogBankEvent(trans, GUILD_BANK_LOG_WITHDRAW_ITEM, BankTab, pl->GetGUIDLow(), pItemBank->GetEntry(), pItemBank->GetCount()); + rankInfo->SetBankMoneyPerDay(moneyPerDay); + } +} - RemoveItem(BankTab, BankTabSlot, trans); - pl->MoveItemToInventory(dest, pItemBank, true); - pl->SaveInventoryAndGoldToDB(trans); +void Guild::_SetRankBankTabRightsAndSlots(uint8 rankId, uint8 tabId, GuildBankRightsAndSlots rightsAndSlots, bool saveToDB) +{ + if (tabId >= _GetPurchasedTabsSize()) + return; - MemberItemWithdraw(BankTab, pl->GetGUIDLow(), trans); - CharacterDatabase.CommitTransaction(trans); - } - else // Bank <-> Char swap items - { - // check source pos rights (item swapped to bank) - if (!IsMemberHaveRights(pl->GetGUIDLow(), BankTab, GUILD_BANK_RIGHT_DEPOSIT_ITEM)) - return; + if (RankInfo* rankInfo = GetRankInfo(rankId)) + { + for (Members::iterator itr = m_members.begin(); itr != m_members.end(); ++itr) + if (itr->second->IsRank(rankId)) + itr->second->ResetTabTimes(); - if (pItemChar) - { - if (!pItemChar->CanBeTraded()) - { - pl->SendEquipError(EQUIP_ERR_ITEMS_CANT_BE_SWAPPED, pItemChar, NULL); - return; - } - } + rankInfo->SetBankTabSlotsAndRights(tabId, rightsAndSlots, saveToDB); + } +} - ItemPosCountVec iDest; - msg = pl->CanStoreItem(PlayerBag, PlayerSlot, iDest, pItemBank, true); - if (msg != EQUIP_ERR_OK) - { - pl->SendEquipError(msg, pItemBank, NULL); - return; - } +inline std::string Guild::_GetRankName(uint8 rankId) const +{ + if (const RankInfo* rankInfo = GetRankInfo(rankId)) + return rankInfo->GetName(); + return ""; +} - GuildItemPosCountVec gDest; - if (pItemChar) - { - msg = CanStoreItem(BankTab, BankTabSlot, gDest, pItemChar->GetCount(), pItemChar, true); - if (msg != EQUIP_ERR_OK) - { - pl->SendEquipError(msg, pItemChar, NULL); - return; - } - } +inline uint32 Guild::_GetRankRights(uint8 rankId) const +{ + if (const RankInfo* rankInfo = GetRankInfo(rankId)) + return rankInfo->GetRights(); + return 0; +} - // check source pos rights (item moved to inventory) - uint32 remRight = GetMemberSlotWithdrawRem(pl->GetGUIDLow(), BankTab); - if (remRight <= 0) - return; +inline uint32 Guild::_GetRankBankMoneyPerDay(uint8 rankId) const +{ + if (const RankInfo* rankInfo = GetRankInfo(rankId)) + return rankInfo->GetBankMoneyPerDay(); + return 0; +} - if (pItemChar) - { - // logging item move to bank - if (pl->GetSession()->GetSecurity() > SEC_PLAYER && sWorld.getBoolConfig(CONFIG_GM_LOG_TRADE)) - { - sLog.outCommand(pl->GetSession()->GetAccountId(), "GM %s (Account: %u) deposit item: %s (Entry: %d Count: %u) to guild bank (Guild ID: %u)", - pl->GetName(), pl->GetSession()->GetAccountId(), - pItemChar->GetProto()->Name1, pItemChar->GetEntry(), pItemChar->GetCount(), - m_Id); - } - } +inline uint32 Guild::_GetRankBankTabSlotsPerDay(uint8 rankId, uint8 tabId) const +{ + if (tabId < _GetPurchasedTabsSize()) + if (const RankInfo* rankInfo = GetRankInfo(rankId)) + return rankInfo->GetBankTabSlotsPerDay(tabId); + return 0; +} - SQLTransaction trans = CharacterDatabase.BeginTransaction(); - LogBankEvent(trans, GUILD_BANK_LOG_WITHDRAW_ITEM, BankTab, pl->GetGUIDLow(), pItemBank->GetEntry(), pItemBank->GetCount()); - if (pItemChar) - LogBankEvent(trans, GUILD_BANK_LOG_DEPOSIT_ITEM, BankTab, pl->GetGUIDLow(), pItemChar->GetEntry(), pItemChar->GetCount()); +inline uint8 Guild::_GetRankBankTabRights(uint8 rankId, uint8 tabId) const +{ + if (const RankInfo* rankInfo = GetRankInfo(rankId)) + return rankInfo->GetBankTabRights(tabId); + return 0; +} - RemoveItem(BankTab, BankTabSlot, trans); - if (pItemChar) - { - pl->MoveItemFromInventory(PlayerBag, PlayerSlot, true); - pItemChar->DeleteFromInventoryDB(trans); - } +inline uint32 Guild::_GetMemberRemainingSlots(const uint64& guid, uint8 tabId) const +{ + if (const Member* pMember = GetMember(guid)) + return pMember->GetBankRemainingValue(tabId, this); + return 0; +} + +inline uint32 Guild::_GetMemberRemainingMoney(const uint64& guid) const +{ + if (const Member* pMember = GetMember(guid)) + return pMember->GetBankRemainingValue(GUILD_BANK_MAX_TABS, this); + return 0; +} - if (pItemChar) - StoreItem(BankTab, gDest, pItemChar, trans); - pl->MoveItemToInventory(iDest, pItemBank, true); - pl->SaveInventoryAndGoldToDB(trans); +inline void Guild::_DecreaseMemberRemainingSlots(SQLTransaction& trans, const uint64& guid, uint8 tabId) +{ + // Remaining slots must be more then 0 + if (uint32 remainingSlots = _GetMemberRemainingSlots(guid, tabId)) + // Ignore guild master + if (remainingSlots < GUILD_WITHDRAW_SLOT_UNLIMITED) + if (Member* pMember = GetMember(guid)) + pMember->DecreaseBankRemainingValue(trans, tabId, 1); +} - MemberItemWithdraw(BankTab, pl->GetGUIDLow(), trans); - CharacterDatabase.CommitTransaction(trans); - } +inline bool Guild::_MemberHasTabRights(const uint64& guid, uint8 tabId, uint32 rights) const +{ + if (const Member* pMember = GetMember(guid)) + { + // Leader always has full rights + if (pMember->IsRank(GR_GUILDMASTER) || m_leaderGuid == guid) + return true; + return (_GetRankBankTabRights(pMember->GetRankId(), tabId) & rights) == rights; } - DisplayGuildBankContentUpdate(BankTab,BankTabSlot); + return false; } -void Guild::MoveFromCharToBank(Player * pl, uint8 PlayerBag, uint8 PlayerSlot, uint8 BankTab, uint8 BankTabSlot, uint32 SplitedAmount) +// Add new event log record +inline void Guild::_LogEvent(GuildEventLogTypes eventType, uint32 playerGuid1, uint32 playerGuid2, uint8 newRank) { - Item *pItemBank = GetItem(BankTab, BankTabSlot); - Item *pItemChar = pl->GetItemByPos(PlayerBag, PlayerSlot); + SQLTransaction trans = CharacterDatabase.BeginTransaction(); + m_eventLog->AddEvent(trans, new EventLogEntry(m_id, m_eventLog->GetNextGUID(), eventType, playerGuid1, playerGuid2, newRank)); + CharacterDatabase.CommitTransaction(trans); + + sScriptMgr.OnGuildEvent(this, uint8(eventType), playerGuid1, playerGuid2, newRank); +} - if (!pItemChar) // Problem to get item from player +// Add new bank event log record +void Guild::_LogBankEvent(SQLTransaction& trans, GuildBankEventLogTypes eventType, uint8 tabId, uint32 lowguid, uint32 itemOrMoney, uint16 itemStackCount, uint8 destTabId) +{ + if (tabId > GUILD_BANK_MAX_TABS) return; - // prevent storing non-empty bags - if (pItemChar && pItemChar->IsBag() && !((Bag*)pItemChar)->IsEmpty()) + uint8 dbTabId = tabId; + if (BankEventLogEntry::IsMoneyEvent(eventType)) { - pl->SendEquipError(EQUIP_ERR_CAN_ONLY_DO_WITH_EMPTY_BAGS, pItemChar, NULL); - return; + tabId = GUILD_BANK_MAX_TABS; + dbTabId = GUILD_BANK_MONEY_LOGS_TAB; } + LogHolder* pLog = m_bankEventLog[tabId]; + pLog->AddEvent(trans, new BankEventLogEntry(m_id, pLog->GetNextGUID(), eventType, dbTabId, lowguid, itemOrMoney, itemStackCount, destTabId)); + + sScriptMgr.OnGuildBankEvent(this, uint8(eventType), tabId, lowguid, itemOrMoney, itemStackCount, destTabId); +} - if (!pItemChar->CanBeTraded()) +inline Item* Guild::_GetItem(uint8 tabId, uint8 slotId) const +{ + if (const BankTab* tab = GetBankTab(tabId)) + return tab->GetItem(slotId); + return NULL; +} + +inline void Guild::_RemoveItem(SQLTransaction& trans, uint8 tabId, uint8 slotId) +{ + if (BankTab* pTab = GetBankTab(tabId)) + pTab->SetItem(trans, slotId, NULL); +} + +void Guild::_MoveItems(MoveItemData* pSrc, MoveItemData* pDest, uint32 splitedAmount) +{ + // 1. Initialize source item + if (!pSrc->InitItem()) + return; // No source item + + // 2. Check source item + if (!pSrc->CheckItem(splitedAmount)) + return; // Source item or splited amount is invalid + /* + if (pItemSrc->GetCount() == 0) { - pl->SendEquipError(EQUIP_ERR_ITEMS_CANT_BE_SWAPPED, pItemChar, NULL); - return; + sLog.outCrash("Guild::SwapItems: Player %s(GUIDLow: %u) tried to move item %u from tab %u slot %u to tab %u slot %u, but item %u has a stack of zero!", + player->GetName(), player->GetGUIDLow(), pItemSrc->GetEntry(), tabId, slotId, destTabId, destSlotId, pItemSrc->GetEntry()); + //return; // Commented out for now, uncomment when it's verified that this causes a crash!! } + //*/ - // check source pos rights (item moved to bank) - if (!IsMemberHaveRights(pl->GetGUIDLow(), BankTab, GUILD_BANK_RIGHT_DEPOSIT_ITEM)) - return; + // 3. Check destination rights + if (!pDest->HasStoreRights(pSrc)) + return; // Player has no rights to store item in destination - if (SplitedAmount > pItemChar->GetCount()) - return; // cheating? - else if (SplitedAmount == pItemChar->GetCount()) - SplitedAmount = 0; // no split + // 4. Check source withdraw rights + if (!pSrc->HasWithdrawRights(pDest)) + return; // Player has no rights to withdraw items from source - if (SplitedAmount) - { // Char -> Bank split to empty or non-empty slot (partly move) - GuildItemPosCountVec dest; - uint8 msg = CanStoreItem(BankTab, BankTabSlot, dest, SplitedAmount, pItemChar, false); - if (msg != EQUIP_ERR_OK) - { - pl->SendEquipError(msg, pItemChar, NULL); - return; - } + // 5. Check split + if (splitedAmount) + { + // 5.1. Clone source item + if (!pSrc->CloneItem(splitedAmount)) + return; // Item could not be cloned - Item *pNewItem = pItemChar->CloneItem(SplitedAmount); - if (!pNewItem) + // 5.2. Move splited item to destination + _DoItemsMove(pSrc, pDest, true, splitedAmount); + } + else // 6. No split + { + // 6.1. Try to merge items in destination (pDest->GetItem() == NULL) + if (!_DoItemsMove(pSrc, pDest, false)) // Item could not be merged { - pl->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, pItemChar, NULL); - return; - } + // 6.2. Try to swap items + // 6.2.1. Initialize destination item + if (!pDest->InitItem()) + return; - // logging item move to bank (before items merge - if (pl->GetSession()->GetSecurity() > SEC_PLAYER && sWorld.getBoolConfig(CONFIG_GM_LOG_TRADE)) - { - sLog.outCommand(pl->GetSession()->GetAccountId(), "GM %s (Account: %u) deposit item: %s (Entry: %d Count: %u) to guild bank (Guild ID: %u)", - pl->GetName(), pl->GetSession()->GetAccountId(), - pItemChar->GetProto()->Name1, pItemChar->GetEntry(), SplitedAmount,m_Id); + // 6.2.2. Check rights to store item in source (opposite direction) + if (!pSrc->HasStoreRights(pDest)) + return; // Player has no rights to store item in source (opposite direction) + + if (!pDest->HasWithdrawRights(pSrc)) + return; // Player has no rights to withdraw item from destination (opposite direction) + + // 6.2.3. Swap items (pDest->GetItem() != NULL) + _DoItemsMove(pSrc, pDest, true); } + } + // 7. Send changes + _SendBankContentUpdate(pSrc, pDest); +} - SQLTransaction trans = CharacterDatabase.BeginTransaction(); - LogBankEvent(trans, GUILD_BANK_LOG_DEPOSIT_ITEM, BankTab, pl->GetGUIDLow(), pItemChar->GetEntry(), SplitedAmount); +bool Guild::_DoItemsMove(MoveItemData* pSrc, MoveItemData* pDest, bool sendError, uint32 splitedAmount) +{ + Item* pDestItem = pDest->GetItem(); + bool swap = (pDestItem != NULL); - pl->ItemRemovedQuestCheck(pItemChar->GetEntry(), SplitedAmount); - pItemChar->SetCount(pItemChar->GetCount()-SplitedAmount); - pItemChar->SetState(ITEM_CHANGED, pl); - pl->SaveInventoryAndGoldToDB(trans); - StoreItem(BankTab, dest, pNewItem, trans); - CharacterDatabase.CommitTransaction(trans); + Item* pSrcItem = pSrc->GetItem(splitedAmount); + // 1. Can store source item in destination + if (!pDest->CanStore(pSrcItem, swap, sendError)) + return false; - DisplayGuildBankContentUpdate(BankTab, dest); - } - else // Char -> Bank swap with empty or non-empty (move) - { - GuildItemPosCountVec dest; - uint8 msg = CanStoreItem(BankTab, BankTabSlot, dest, pItemChar->GetCount(), pItemChar, false); - if (msg == EQUIP_ERR_OK) // merge - { - // logging item move to bank - if (pl->GetSession()->GetSecurity() > SEC_PLAYER && sWorld.getBoolConfig(CONFIG_GM_LOG_TRADE)) - { - sLog.outCommand(pl->GetSession()->GetAccountId(), "GM %s (Account: %u) deposit item: %s (Entry: %d Count: %u) to guild bank (Guild ID: %u)", - pl->GetName(), pl->GetSession()->GetAccountId(), - pItemChar->GetProto()->Name1, pItemChar->GetEntry(), pItemChar->GetCount(), - m_Id); - } + // 2. Can store destination item in source + if (swap) + if (!pSrc->CanStore(pDestItem, true, true)) + return false; - SQLTransaction trans = CharacterDatabase.BeginTransaction(); - LogBankEvent(trans, GUILD_BANK_LOG_DEPOSIT_ITEM, BankTab, pl->GetGUIDLow(), pItemChar->GetEntry(), pItemChar->GetCount()); + // GM LOG (TODO: move to scripts) + pDest->LogAction(pSrc); + if (swap) + pSrc->LogAction(pDest); - pl->MoveItemFromInventory(PlayerBag, PlayerSlot, true); - pItemChar->DeleteFromInventoryDB(trans); + SQLTransaction trans = CharacterDatabase.BeginTransaction(); + // 3. Log bank events + pDest->LogBankEvent(trans, pSrc, pSrcItem->GetCount()); + if (swap) + pSrc->LogBankEvent(trans, pDest, pDestItem->GetCount()); - StoreItem(BankTab, dest, pItemChar, trans); - pl->SaveInventoryAndGoldToDB(trans); - CharacterDatabase.CommitTransaction(trans); + // 4. Remove item from source + pSrc->RemoveItem(trans, pDest, splitedAmount); - DisplayGuildBankContentUpdate(BankTab, dest); - } - else // Char <-> Bank swap items (posible NULL bank item) + // 5. Remove item from destination + if (swap) + pDest->RemoveItem(trans, pSrc); + + // 6. Store item in destination + pDest->StoreItem(trans, pSrcItem); + + // 7. Store item in source + if (swap) + pSrc->StoreItem(trans, pDestItem); + + CharacterDatabase.CommitTransaction(trans); + return true; +} + +void Guild::_SendBankContent(WorldSession *session, uint8 tabId) const +{ + const uint64& guid = session->GetPlayer()->GetGUID(); + if (_MemberHasTabRights(guid, tabId, GUILD_BANK_RIGHT_VIEW_TAB)) + if (const BankTab* pTab = GetBankTab(tabId)) { - ItemPosCountVec iDest; - if (pItemBank) - { - msg = pl->CanStoreItem(PlayerBag, PlayerSlot, iDest, pItemBank, true); - if (msg != EQUIP_ERR_OK) - { - pl->SendEquipError(msg, pItemBank, NULL); - return; - } - } + WorldPacket data(SMSG_GUILD_BANK_LIST, 1200); - GuildItemPosCountVec gDest; - msg = CanStoreItem(BankTab, BankTabSlot, gDest, pItemChar->GetCount(), pItemChar, true); - if (msg != EQUIP_ERR_OK) - { - pl->SendEquipError(msg, pItemChar, NULL); - return; - } + data << uint64(m_bankMoney); + data << uint8(tabId); + data << uint32(_GetMemberRemainingSlots(guid, tabId)); + data << uint8(0); // Tell client that there's no tab info in this packet - if (pItemBank) - { - // check bank pos rights (item swapped with inventory) - uint32 remRight = GetMemberSlotWithdrawRem(pl->GetGUIDLow(), BankTab); - if (remRight <= 0) - return; - } + pTab->WritePacket(data); - // logging item move to bank - if (pl->GetSession()->GetSecurity() > SEC_PLAYER && sWorld.getBoolConfig(CONFIG_GM_LOG_TRADE)) - { - sLog.outCommand(pl->GetSession()->GetAccountId(), "GM %s (Account: %u) deposit item: %s (Entry: %d Count: %u) to guild bank (Guild ID: %u)", - pl->GetName(), pl->GetSession()->GetAccountId(), - pItemChar->GetProto()->Name1, pItemChar->GetEntry(), pItemChar->GetCount(), - m_Id); - } + session->SendPacket(&data); - SQLTransaction trans = CharacterDatabase.BeginTransaction(); - if (pItemBank) - LogBankEvent(trans, GUILD_BANK_LOG_WITHDRAW_ITEM, BankTab, pl->GetGUIDLow(), pItemBank->GetEntry(), pItemBank->GetCount()); - LogBankEvent(trans, GUILD_BANK_LOG_DEPOSIT_ITEM, BankTab, pl->GetGUIDLow(), pItemChar->GetEntry(), pItemChar->GetCount()); - - pl->MoveItemFromInventory(PlayerBag, PlayerSlot, true); - pItemChar->DeleteFromInventoryDB(trans); - if (pItemBank) - RemoveItem(BankTab, BankTabSlot, trans); - - StoreItem(BankTab,gDest,pItemChar, trans); - if (pItemBank) - pl->MoveItemToInventory(iDest, pItemBank, true); - pl->SaveInventoryAndGoldToDB(trans); - if (pItemBank) - MemberItemWithdraw(BankTab, pl->GetGUIDLow(), trans); - CharacterDatabase.CommitTransaction(trans); - - DisplayGuildBankContentUpdate(BankTab, gDest); + sLog.outDebug("WORLD: Sent (SMSG_GUILD_BANK_LIST)"); } - } } -void Guild::BroadcastEvent(GuildEvents event, uint64 guid, uint8 strCount, std::string str1, std::string str2, std::string str3) +void Guild::_SendBankMoneyUpdate(WorldSession *session) const { - WorldPacket data(SMSG_GUILD_EVENT, 1+1+(guid ? 8 : 0)); - data << uint8(event); - data << uint8(strCount); - - switch(strCount) - { - case 0: - break; - case 1: - data << str1; - break; - case 2: - data << str1 << str2; - break; - case 3: - data << str1 << str2 << str3; - break; - default: - sLog.outError("Guild::BroadcastEvent: incorrect strings count %u!", strCount); - break; - } - - if (guid) - data << uint64(guid); + WorldPacket data(SMSG_GUILD_BANK_LIST, 8 + 1 + 4 + 1 + 1); + data << uint64(m_bankMoney); + data << uint8(0); // tabId, default 0 + data << uint32(_GetMemberRemainingSlots(session->GetPlayer()->GetGUID(), 0)); + data << uint8(0); // Tell that there's no tab info in this packet + data << uint8(0); // No items BroadcastPacket(&data); - sLog.outDebug("WORLD: Sent SMSG_GUILD_EVENT"); + sLog.outDebug("WORLD: Sent (SMSG_GUILD_BANK_LIST)"); } -void Guild::DeleteGuildBankItems(SQLTransaction& trans, bool alsoInDB /*= false*/) +void Guild::_SendBankContentUpdate(MoveItemData* pSrc, MoveItemData* pDest) const { - for (size_t i = 0; i < m_TabListMap.size(); ++i) + ASSERT(pSrc->IsBank() || pDest->IsBank()); + + uint8 tabId; + SlotIds slots; + if (pSrc->IsBank()) // B -> { - for (uint8 j = 0; j < GUILD_BANK_MAX_SLOTS; ++j) + tabId = pSrc->GetContainer(); + slots.insert(pSrc->GetSlotId()); + if (pDest->IsBank()) // B -> B { - if (Item*& pItem = m_TabListMap[i]->Slots[j]) + // Same tab - add destination slots to collection + if (pDest->GetContainer() == pSrc->GetContainer()) + pDest->CopySlots(slots); + else // Different tabs - send second message { - pItem->RemoveFromWorld(); - - if (alsoInDB) - pItem->DeleteFromDB(trans); - - delete pItem; - pItem = NULL; + SlotIds destSlots; + pDest->CopySlots(destSlots); + _SendBankContentUpdate(pDest->GetContainer(), destSlots); } } - delete m_TabListMap[i]; - m_TabListMap[i] = NULL; } - m_TabListMap.clear(); + else if (pDest->IsBank()) // C -> B + { + tabId = pDest->GetContainer(); + pDest->CopySlots(slots); + } + _SendBankContentUpdate(tabId, slots); } -bool GuildItemPosCount::isContainedIn(GuildItemPosCountVec const &vec) const +void Guild::_SendBankContentUpdate(uint8 tabId, SlotIds slots) const { - for (GuildItemPosCountVec::const_iterator itr = vec.begin(); itr != vec.end(); ++itr) - if (itr->Slot == this->Slot) - return true; + if (const BankTab* pTab = GetBankTab(tabId)) + { + WorldPacket data(SMSG_GUILD_BANK_LIST, 1200); - return false; + data << uint64(m_bankMoney); + data << uint8(tabId); + + size_t rempos = data.wpos(); + data << uint32(0); // Item withdraw amount, will be filled later + data << uint8(0); // Tell client that there's no tab info in this packet + + data << uint8(slots.size()); + for (uint8 slotId = 0; slotId < GUILD_BANK_MAX_SLOTS; ++slotId) + if (slots.find(slotId) != slots.end()) + pTab->WriteSlotPacket(data, slotId); + + for (Members::const_iterator itr = m_members.begin(); itr != m_members.end(); ++itr) + if (_MemberHasTabRights(itr->second->GetGUID(), tabId, GUILD_BANK_RIGHT_VIEW_TAB)) + if (Player *player = itr->second->FindPlayer()) + { + data.put(rempos, uint32(_GetMemberRemainingSlots(player->GetGUID(), tabId))); + player->GetSession()->SendPacket(&data); + } + + sLog.outDebug("WORLD: Sent (SMSG_GUILD_BANK_LIST)"); + } +} + +void Guild::_BroadcastEvent(GuildEvents guildEvent, const uint64& guid, const char* param1, const char* param2, const char* param3) const +{ + uint8 count = !param3 ? (!param2 ? (!param1 ? 0 : 1) : 2) : 3; + + WorldPacket data(SMSG_GUILD_EVENT, 1 + 1 + count + (guid ? 8 : 0)); + data << uint8(guildEvent); + data << uint8(count); + + if (param3) + data << param1 << param2 << param3; + else if (param2) + data << param1 << param2; + else if (param1) + data << param1; + + if (guid) + data << uint64(guid); + + BroadcastPacket(&data); + + sLog.outDebug("WORLD: Sent SMSG_GUILD_EVENT"); } -- cgit v1.2.3