account bank
This commit is contained in:
@@ -1344,7 +1344,7 @@ bool Item::CanBeTraded(bool mail, bool trade) const
|
||||
if (m_lootGenerated)
|
||||
return false;
|
||||
|
||||
if ((!mail || !IsBoundAccountWide()) && (IsSoulBound() && (!IsBOPTradeable() || !trade)))
|
||||
if ((!mail || !IsAccountBound()) && (IsSoulBound() && (!IsBOPTradeable() || !trade)))
|
||||
return false;
|
||||
|
||||
if (IsBag() && (Player::IsBagPos(GetPos()) || !ToBag()->IsEmpty()))
|
||||
@@ -1781,7 +1781,7 @@ bool Item::IsBindedNotWith(Player const* player) const
|
||||
return false;
|
||||
|
||||
// BOA item case
|
||||
if (IsBoundAccountWide())
|
||||
if (IsAccountBound())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
@@ -3187,3 +3187,25 @@ void BonusData::AddBonus(uint32 type, std::array<int32, 4> const& values)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool Item::IsWarbandBound() const
|
||||
{
|
||||
ItemBondingType bonding = GetBonding();
|
||||
if (bonding == BIND_WOW_ACCOUNT || bonding == BIND_BNET_ACCOUNT)
|
||||
return true;
|
||||
if (bonding == BIND_BNET_ACCOUNT_UNTIL_EQUIPPED && !HasItemFlag(ITEM_FIELD_FLAG_CONVERTED_WARBOUND))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void Item::ConvertToSoulbound()
|
||||
{
|
||||
if (GetBonding() != BIND_BNET_ACCOUNT_UNTIL_EQUIPPED)
|
||||
return;
|
||||
if (HasItemFlag(ITEM_FIELD_FLAG_CONVERTED_WARBOUND))
|
||||
return;
|
||||
|
||||
SetBinding(true);
|
||||
SetItemFlag(ITEM_FIELD_FLAG_CONVERTED_WARBOUND);
|
||||
SetState(ITEM_CHANGED, GetOwner());
|
||||
}
|
||||
|
||||
@@ -227,6 +227,11 @@ class TC_GAME_API Item : public Object
|
||||
bool IsSoulBound() const { return HasItemFlag(ITEM_FIELD_FLAG_SOULBOUND); }
|
||||
bool IsBoundAccountWide() const { return GetTemplate()->HasFlag(ITEM_FLAG_IS_BOUND_TO_ACCOUNT); }
|
||||
bool IsBattlenetAccountBound() const { return GetTemplate()->HasFlag(ITEM_FLAG2_BNET_ACCOUNT_TRADE_OK); }
|
||||
//WowCommunity
|
||||
bool IsWarbandBound() const;
|
||||
bool IsAccountBound() const { return IsBoundAccountWide() || IsWarbandBound(); }
|
||||
void ConvertToSoulbound();
|
||||
//WowCommunity
|
||||
bool IsBindedNotWith(Player const* player) const;
|
||||
bool IsBoundByEnchant() const;
|
||||
virtual void SaveToDB(CharacterDatabaseTransaction trans);
|
||||
|
||||
@@ -131,9 +131,9 @@ enum ItemBondingType
|
||||
BIND_QUEST = 4,
|
||||
BIND_UNUSED_1 = 5,
|
||||
BIND_UNUSED_2 = 6,
|
||||
BIND_WOW_ACCOUNT = 7,
|
||||
BIND_BNET_ACCOUNT = 8,
|
||||
BIND_BNET_ACCOUNT_UNTIL_EQUIPPED = 9,
|
||||
BIND_WOW_ACCOUNT = 7,// Bind to Warband (WoW account scope)
|
||||
BIND_BNET_ACCOUNT = 8,// Bind to Warband (Battle.net account scope)
|
||||
BIND_BNET_ACCOUNT_UNTIL_EQUIPPED = 9,// Bind to Warband until equipped, then becomes soulbound
|
||||
};
|
||||
|
||||
/* /// @todo: Requiring actual cases in which using (an) item isn't allowed while shapeshifted. Else, this flag would need an implementation.
|
||||
@@ -146,7 +146,7 @@ enum ItemFieldFlags : uint32
|
||||
ITEM_FIELD_FLAG_TRANSLATED = 0x00000002, // Item text will not read as garbage when player does not know the language
|
||||
ITEM_FIELD_FLAG_UNLOCKED = 0x00000004, // Item had lock but can be opened now
|
||||
ITEM_FIELD_FLAG_WRAPPED = 0x00000008, // Item is wrapped and contains another item
|
||||
ITEM_FIELD_FLAG_UNK2 = 0x00000010,
|
||||
ITEM_FIELD_FLAG_CONVERTED_WARBOUND = 0x00000010, // BtWuE item has been equipped and converted to soulbound
|
||||
ITEM_FIELD_FLAG_UNK3 = 0x00000020,
|
||||
ITEM_FIELD_FLAG_UNK4 = 0x00000040,
|
||||
ITEM_FIELD_FLAG_UNK5 = 0x00000080,
|
||||
|
||||
@@ -9990,6 +9990,8 @@ bool Player::IsEquipmentPos(uint8 bag, uint8 slot)
|
||||
return true;
|
||||
if (bag == INVENTORY_SLOT_BAG_0 && (slot >= REAGENT_BAG_SLOT_START && slot < REAGENT_BAG_SLOT_END))
|
||||
return true;
|
||||
if (bag == INVENTORY_SLOT_BAG_0 && (slot >= ACCOUNT_BANK_SLOT_BAG_START && slot < ACCOUNT_BANK_SLOT_BAG_END))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -10020,10 +10022,12 @@ bool Player::IsChildEquipmentPos(uint8 bag, uint8 slot)
|
||||
return bag == INVENTORY_SLOT_BAG_0 && (slot >= CHILD_EQUIPMENT_SLOT_START && slot < CHILD_EQUIPMENT_SLOT_END);
|
||||
}
|
||||
|
||||
bool Player::IsAccountBankPos(uint8 bag, uint8 /*slot*/)
|
||||
bool Player::IsAccountBankPos(uint8 bag, uint8 slot)
|
||||
{
|
||||
if (bag >= ACCOUNT_BANK_SLOT_BAG_START && bag < ACCOUNT_BANK_SLOT_BAG_END)
|
||||
return true;
|
||||
if (bag == INVENTORY_SLOT_BAG_0 && slot >= ACCOUNT_BANK_SLOT_BAG_START && slot < ACCOUNT_BANK_SLOT_BAG_END)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -10063,11 +10067,16 @@ bool Player::IsValidPos(uint8 bag, uint8 slot, bool explicit_pos) const
|
||||
if (slot >= BANK_SLOT_BAG_START && slot < BANK_SLOT_BAG_END)
|
||||
return true;
|
||||
|
||||
// account bank bag slots
|
||||
if (slot >= ACCOUNT_BANK_SLOT_BAG_START && slot < ACCOUNT_BANK_SLOT_BAG_END)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// bag content slots
|
||||
// bank bag content slots
|
||||
// account bank bag content slots
|
||||
if (Bag* pBag = GetBagByPos(bag))
|
||||
{
|
||||
// any post selected
|
||||
@@ -11719,6 +11728,9 @@ Item* Player::_StoreItem(uint16 pos, Item* pItem, uint32 count, bool clone, bool
|
||||
|
||||
if (pItem->GetBonding() == BIND_ON_ACQUIRE ||
|
||||
pItem->GetBonding() == BIND_QUEST ||
|
||||
pItem->GetBonding() == BIND_WOW_ACCOUNT ||
|
||||
pItem->GetBonding() == BIND_BNET_ACCOUNT ||
|
||||
pItem->GetBonding() == BIND_BNET_ACCOUNT_UNTIL_EQUIPPED ||
|
||||
(pItem->GetBonding() == BIND_ON_EQUIP && IsBagPos(pos)))
|
||||
pItem->SetBinding(true);
|
||||
|
||||
@@ -11758,6 +11770,9 @@ Item* Player::_StoreItem(uint16 pos, Item* pItem, uint32 count, bool clone, bool
|
||||
{
|
||||
if (pItem2->GetBonding() == BIND_ON_ACQUIRE ||
|
||||
pItem2->GetBonding() == BIND_QUEST ||
|
||||
pItem2->GetBonding() == BIND_WOW_ACCOUNT ||
|
||||
pItem2->GetBonding() == BIND_BNET_ACCOUNT ||
|
||||
pItem2->GetBonding() == BIND_BNET_ACCOUNT_UNTIL_EQUIPPED ||
|
||||
(pItem2->GetBonding() == BIND_ON_EQUIP && IsBagPos(pos)))
|
||||
pItem2->SetBinding(true);
|
||||
|
||||
@@ -12180,9 +12195,13 @@ void Player::VisualizeItem(uint8 slot, Item* pItem)
|
||||
return;
|
||||
|
||||
// check also BIND_ON_ACQUIRE and BIND_QUEST for .additem or .additemset case by GM (not binded at adding to inventory)
|
||||
if (pItem->GetBonding() == BIND_ON_EQUIP || pItem->GetBonding() == BIND_ON_ACQUIRE || pItem->GetBonding() == BIND_QUEST)
|
||||
if (pItem->GetBonding() == BIND_ON_EQUIP || pItem->GetBonding() == BIND_ON_ACQUIRE || pItem->GetBonding() == BIND_QUEST
|
||||
|| pItem->GetBonding() == BIND_WOW_ACCOUNT || pItem->GetBonding() == BIND_BNET_ACCOUNT
|
||||
|| pItem->GetBonding() == BIND_BNET_ACCOUNT_UNTIL_EQUIPPED)
|
||||
{
|
||||
pItem->SetBinding(true);
|
||||
if (pItem->GetBonding() == BIND_BNET_ACCOUNT_UNTIL_EQUIPPED)
|
||||
pItem->ConvertToSoulbound();
|
||||
if (IsInWorld())
|
||||
GetSession()->GetCollectionMgr()->AddItemAppearance(pItem);
|
||||
}
|
||||
@@ -18833,6 +18852,13 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol
|
||||
if (!m_taxi.LoadTaxiMask(fields.taximask)) // must be before InitTaxiNodesForLevel
|
||||
TC_LOG_WARN("entities.player.loading", "Player::LoadFromDB: Player ({}) has invalid taximask ({}) in DB. Forced partial load.", GetGUID().ToString(), fields.taximask);
|
||||
|
||||
//WowCommunity
|
||||
if (PreparedQueryResult warbandTaxiResult = holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_WARBAND_TAXI_MASK))
|
||||
{
|
||||
TaxiMask accountMask = PlayerTaxi::LoadTaxiMaskFromString((*warbandTaxiResult)[0].GetString());
|
||||
m_taxi.MergeAccountTaxiMask(accountMask);
|
||||
}
|
||||
//WowCommunity
|
||||
uint32 extraflags = fields.extra_flags;
|
||||
|
||||
_LoadPetStable(fields.summonedPetNumber, holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_PET_SLOTS));
|
||||
@@ -18930,6 +18956,7 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol
|
||||
m_reputationMgr->LoadFromDB(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_REPUTATION));
|
||||
|
||||
_LoadCharacterBankTabSettings(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_BANK_TAB_SETTINGS));
|
||||
_LoadAccountBankTabSettings(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_ACCOUNT_BANK_TAB_SETTINGS));
|
||||
|
||||
_LoadInventory(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_INVENTORY),
|
||||
holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_ARTIFACTS),
|
||||
@@ -18939,6 +18966,8 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol
|
||||
holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_AZERITE_EMPOWERED),
|
||||
time_diff);
|
||||
|
||||
_LoadAccountBankItems(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_ACCOUNT_BANK_ITEMS), time_diff);
|
||||
|
||||
// update items with duration and realtime
|
||||
UpdateItemDuration(time_diff, true);
|
||||
|
||||
@@ -21458,6 +21487,19 @@ void Player::SaveToDB(LoginDatabaseTransaction loginTransaction, CharacterDataba
|
||||
_SaveCUFProfiles(trans);
|
||||
_SavePlayerData(trans);
|
||||
_SaveCharacterBankTabSettings(trans);
|
||||
//WowCommunity
|
||||
_SaveAccountBankTabSettings(trans);
|
||||
_SaveAccountBankItems(trans);
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << m_taxi;
|
||||
CharacterDatabasePreparedStatement* taxiStmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_WARBAND_TAXI_MASK);
|
||||
taxiStmt->setUInt32(0, GetSession()->GetBattlenetAccountId());
|
||||
taxiStmt->setString(1, ss.str());
|
||||
trans->Append(taxiStmt);
|
||||
}
|
||||
//WowCommunity
|
||||
|
||||
for (auto const& [type, garrison] : _garrisons)
|
||||
garrison->SaveToDB(trans);
|
||||
for (auto const& h : _housings)
|
||||
@@ -21776,21 +21818,31 @@ void Player::_SaveInventory(CharacterDatabaseTransaction trans)
|
||||
}
|
||||
}
|
||||
|
||||
// Account bank items are saved separately in _SaveAccountBankItems ? skip inventory position save
|
||||
bool isAccountBankItem = IsAccountBankPos(item->GetBagSlot(), item->GetSlot())
|
||||
|| (container && IsAccountBankPos(INVENTORY_SLOT_BAG_0, container->GetSlot()));
|
||||
|
||||
switch (item->GetState())
|
||||
{
|
||||
case ITEM_NEW:
|
||||
case ITEM_CHANGED:
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_INVENTORY_ITEM);
|
||||
stmt->setUInt64(0, GetGUID().GetCounter());
|
||||
stmt->setUInt64(1, container ? container->GetGUID().GetCounter() : UI64LIT(0));
|
||||
stmt->setUInt8 (2, item->GetSlot());
|
||||
stmt->setUInt64(3, item->GetGUID().GetCounter());
|
||||
trans->Append(stmt);
|
||||
if (!isAccountBankItem)
|
||||
{
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_INVENTORY_ITEM);
|
||||
stmt->setUInt64(0, GetGUID().GetCounter());
|
||||
stmt->setUInt64(1, container ? container->GetGUID().GetCounter() : UI64LIT(0));
|
||||
stmt->setUInt8(2, item->GetSlot());
|
||||
stmt->setUInt64(3, item->GetGUID().GetCounter());
|
||||
trans->Append(stmt);
|
||||
}
|
||||
break;
|
||||
case ITEM_REMOVED:
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INVENTORY_BY_ITEM);
|
||||
stmt->setUInt64(0, item->GetGUID().GetCounter());
|
||||
trans->Append(stmt);
|
||||
if (!isAccountBankItem)
|
||||
{
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_INVENTORY_BY_ITEM);
|
||||
stmt->setUInt64(0, item->GetGUID().GetCounter());
|
||||
trans->Append(stmt);
|
||||
}
|
||||
break;
|
||||
case ITEM_UNCHANGED:
|
||||
break;
|
||||
@@ -32867,7 +32919,9 @@ bool Player::ProcessItemCast(SpellCastRequest& castRequest, SpellCastTargets con
|
||||
}
|
||||
|
||||
// check also BIND_ON_ACQUIRE and BIND_QUEST for .additem or .additemset case by GM (not binded at adding to inventory)
|
||||
if (item->GetBonding() == BIND_ON_USE || item->GetBonding() == BIND_ON_ACQUIRE || item->GetBonding() == BIND_QUEST)
|
||||
if (item->GetBonding() == BIND_ON_USE || item->GetBonding() == BIND_ON_ACQUIRE || item->GetBonding() == BIND_QUEST
|
||||
|| item->GetBonding() == BIND_WOW_ACCOUNT || item->GetBonding() == BIND_BNET_ACCOUNT
|
||||
|| item->GetBonding() == BIND_BNET_ACCOUNT_UNTIL_EQUIPPED)
|
||||
{
|
||||
if (!item->IsSoulBound())
|
||||
{
|
||||
@@ -33100,3 +33154,268 @@ void Player::ResummonAnimalCompanionIfAny()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Item*> Player::GetWarboundItemsToDeposit()
|
||||
{
|
||||
std::vector<Item*> itemList;
|
||||
ForEachItem(ItemSearchLocation::Inventory, [&itemList](Item* item)
|
||||
{
|
||||
// Only deposit warbound/BoA items, not character-soulbound
|
||||
if (item->IsAccountBound() || !item->IsSoulBound())
|
||||
{
|
||||
// Skip quest items and bags
|
||||
if (item->GetTemplate()->GetClass() != ITEM_CLASS_QUEST && !item->IsNotEmptyBag())
|
||||
itemList.push_back(item);
|
||||
}
|
||||
|
||||
return ItemSearchCallbackResult::Continue;
|
||||
});
|
||||
|
||||
return itemList;
|
||||
}
|
||||
|
||||
InventoryResult Player::CanAccountBankItem(uint8 bag, uint8 slot, ItemPosCountVec& dest, Item* pItem, bool swap) const
|
||||
{
|
||||
if (!pItem)
|
||||
return swap ? EQUIP_ERR_CANT_SWAP : EQUIP_ERR_ITEM_NOT_FOUND;
|
||||
|
||||
// Check: no character-soulbound items allowed (only warbound/BoA/unbound)
|
||||
if (pItem->IsSoulBound() && !pItem->IsAccountBound())
|
||||
return EQUIP_ERR_NO_SOULBOUND_ITEM_IN_ACCOUNT_BANK;
|
||||
|
||||
// Check: no quest items in account bank
|
||||
if (pItem->GetTemplate()->GetClass() == ITEM_CLASS_QUEST)
|
||||
return EQUIP_ERR_CANT_SWAP;
|
||||
|
||||
uint32 count = pItem->GetCount();
|
||||
ItemTemplate const* pProto = pItem->GetTemplate();
|
||||
|
||||
// Specific slot requested
|
||||
if (bag != NULL_BAG && slot != NULL_SLOT)
|
||||
{
|
||||
if (bag >= ACCOUNT_BANK_SLOT_BAG_START && bag < ACCOUNT_BANK_SLOT_BAG_END)
|
||||
{
|
||||
InventoryResult res = CanStoreItem_InSpecificSlot(bag, slot, dest, pProto, count, swap, pItem);
|
||||
if (res != EQUIP_ERR_OK)
|
||||
return res;
|
||||
|
||||
if (count == 0)
|
||||
return EQUIP_ERR_OK;
|
||||
}
|
||||
return EQUIP_ERR_BANK_FULL;
|
||||
}
|
||||
|
||||
// Specific bag requested
|
||||
if (bag != NULL_BAG && slot == NULL_SLOT)
|
||||
{
|
||||
if (bag >= ACCOUNT_BANK_SLOT_BAG_START && bag < ACCOUNT_BANK_SLOT_BAG_END)
|
||||
{
|
||||
// Try merge first, then empty slots
|
||||
InventoryResult res = CanStoreItem_InBag(bag, dest, pProto, count, true, false, pItem, NULL_BAG, NULL_SLOT);
|
||||
if (res != EQUIP_ERR_OK)
|
||||
res = CanStoreItem_InBag(bag, dest, pProto, count, false, false, pItem, NULL_BAG, NULL_SLOT);
|
||||
|
||||
if (res != EQUIP_ERR_OK)
|
||||
return res;
|
||||
|
||||
if (count == 0)
|
||||
return EQUIP_ERR_OK;
|
||||
}
|
||||
return EQUIP_ERR_BANK_FULL;
|
||||
}
|
||||
|
||||
// No specific bag/slot: search all account bank bags
|
||||
// First pass: try to merge with existing stacks
|
||||
for (uint8 i = ACCOUNT_BANK_SLOT_BAG_START; i < ACCOUNT_BANK_SLOT_BAG_END; i++)
|
||||
{
|
||||
InventoryResult res = CanStoreItem_InBag(i, dest, pProto, count, true, false, pItem, bag, slot);
|
||||
if (res != EQUIP_ERR_OK)
|
||||
continue;
|
||||
|
||||
if (count == 0)
|
||||
return EQUIP_ERR_OK;
|
||||
}
|
||||
|
||||
// Second pass: try empty slots
|
||||
for (uint8 i = ACCOUNT_BANK_SLOT_BAG_START; i < ACCOUNT_BANK_SLOT_BAG_END; i++)
|
||||
{
|
||||
InventoryResult res = CanStoreItem_InBag(i, dest, pProto, count, false, true, pItem, bag, slot);
|
||||
if (res != EQUIP_ERR_OK)
|
||||
continue;
|
||||
|
||||
if (count == 0)
|
||||
return EQUIP_ERR_OK;
|
||||
}
|
||||
|
||||
return EQUIP_ERR_BANK_FULL;
|
||||
}
|
||||
|
||||
void Player::_LoadAccountBankTabSettings(PreparedQueryResult result)
|
||||
{
|
||||
uint8 tabCount = 0;
|
||||
|
||||
if (result)
|
||||
{
|
||||
do
|
||||
{
|
||||
DEFINE_FIELD_ACCESSOR_CACHE_ANONYMOUS(PreparedResultSet, (tabId)(name)(icon)(description)(depositFlags)) fields { *result };
|
||||
|
||||
uint8 tabId = fields.tabId().GetUInt8();
|
||||
if (tabId >= (ACCOUNT_BANK_SLOT_BAG_END - ACCOUNT_BANK_SLOT_BAG_START))
|
||||
continue;
|
||||
|
||||
SetAccountBankTabSettings(tabId, fields.name().GetString(), fields.icon().GetString(),
|
||||
fields.description().GetString(), static_cast<BagSlotFlags>(fields.depositFlags().GetUInt32()));
|
||||
|
||||
if (tabId >= tabCount)
|
||||
tabCount = tabId + 1;
|
||||
|
||||
} while (result->NextRow());
|
||||
}
|
||||
|
||||
// Derive tab count from the rows loaded
|
||||
SetAccountBankTabCount(tabCount);
|
||||
|
||||
while (m_activePlayerData->AccountBankTabSettings.size() < *m_activePlayerData->NumAccountBankTabs)
|
||||
AddDynamicUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::AccountBankTabSettings));
|
||||
}
|
||||
|
||||
void Player::_LoadAccountBankItems(PreparedQueryResult result, uint32 timeDiff)
|
||||
{
|
||||
// Same field layout as character_inventory load, but with bag/slot from account_bank_item at the end
|
||||
// Fields 0-51: item_instance fields (same as _LoadInventory)
|
||||
// Field 52: abi.bag (tab index 0-4)
|
||||
// Field 53: abi.slot (slot within tab 0-97)
|
||||
|
||||
if (!result)
|
||||
return;
|
||||
|
||||
uint32 zoneId = GetZoneId();
|
||||
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
|
||||
|
||||
m_itemUpdateQueueBlocked = true;
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
if (Item* item = _LoadItem(trans, zoneId, timeDiff, fields))
|
||||
{
|
||||
uint8 tabIndex = fields[52].GetUInt8();
|
||||
uint8 slot = fields[53].GetUInt8();
|
||||
|
||||
if (tabIndex >= GetAccountBankTabCount())
|
||||
{
|
||||
TC_LOG_ERROR("entities.player", "Player::_LoadAccountBankItems: Player '{}' ({}) has account bank item ({}, entry: {}) in tab {} which exceeds tab count {}. Skipping.",
|
||||
GetName(), GetGUID().ToString(), item->GetGUID().ToString(), item->GetEntry(), tabIndex, GetAccountBankTabCount());
|
||||
item->DeleteFromDB(trans);
|
||||
delete item;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8 bagSlot = ACCOUNT_BANK_SLOT_BAG_START + tabIndex;
|
||||
|
||||
// Ensure the bag exists at this position
|
||||
Bag* bag = GetBagByPos(bagSlot);
|
||||
if (!bag)
|
||||
{
|
||||
// Create the bag item for this tab if it doesn't exist yet
|
||||
if (Item* bagItem = Item::CreateItem(ITEM_ACCOUNT_BANK_TAB_BAG, 1, ItemContext::NONE, this))
|
||||
{
|
||||
uint16 bagPos = (INVENTORY_SLOT_BAG_0 << 8) | bagSlot;
|
||||
bagItem->SetContainer(nullptr);
|
||||
bagItem->SetSlot(bagSlot);
|
||||
StoreItem(ItemPosCountVec(1, ItemPosCount(bagPos, 1)), bagItem, true);
|
||||
bagItem->SetState(ITEM_UNCHANGED, this);
|
||||
bag = bagItem->ToBag();
|
||||
}
|
||||
|
||||
if (!bag)
|
||||
{
|
||||
TC_LOG_ERROR("entities.player", "Player::_LoadAccountBankItems: Player '{}' ({}) failed to create account bank bag for tab {}.",
|
||||
GetName(), GetGUID().ToString(), tabIndex);
|
||||
item->DeleteFromDB(trans);
|
||||
delete item;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
GetSession()->GetCollectionMgr()->CheckHeirloomUpgrades(item);
|
||||
GetSession()->GetCollectionMgr()->AddItemAppearance(item);
|
||||
|
||||
ItemPosCountVec dest;
|
||||
InventoryResult err = CanStoreItem(bagSlot, slot, dest, item);
|
||||
if (err == EQUIP_ERR_OK)
|
||||
{
|
||||
item = StoreItem(dest, item, true);
|
||||
item->SetState(ITEM_UNCHANGED, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
TC_LOG_ERROR("entities.player", "Player::_LoadAccountBankItems: Player '{}' ({}) has account bank item ({}, entry: {}) which can't be loaded (tab {}, slot {}) by reason {}. Item will be sent by mail.",
|
||||
GetName(), GetGUID().ToString(), item->GetGUID().ToString(), item->GetEntry(), tabIndex, slot, uint32(err));
|
||||
item->DeleteFromInventoryDB(trans);
|
||||
|
||||
MailDraft draft(GetSession()->GetTrinityString(LANG_NOT_EQUIPPED_ITEM), "There were problems with equipping item(s).");
|
||||
draft.AddItem(item);
|
||||
draft.SendMailTo(trans, this, MailSender(this, MAIL_STATIONERY_GM), MAIL_CHECK_MASK_COPIED);
|
||||
}
|
||||
}
|
||||
} while (result->NextRow());
|
||||
|
||||
m_itemUpdateQueueBlocked = false;
|
||||
CharacterDatabase.CommitTransaction(trans);
|
||||
}
|
||||
|
||||
void Player::_SaveAccountBankTabSettings(CharacterDatabaseTransaction trans) const
|
||||
{
|
||||
uint32 bnetAccountId = GetSession()->GetBattlenetAccountId();
|
||||
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ACCOUNT_BANK_TAB_SETTINGS);
|
||||
stmt->setUInt32(0, bnetAccountId);
|
||||
trans->Append(stmt);
|
||||
|
||||
for (std::size_t i = 0; i < m_activePlayerData->AccountBankTabSettings.size(); ++i)
|
||||
{
|
||||
UF::BankTabSettings const& tabSetting = m_activePlayerData->AccountBankTabSettings[i];
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_ACCOUNT_BANK_TAB_SETTINGS);
|
||||
stmt->setUInt32(0, bnetAccountId);
|
||||
stmt->setUInt8(1, i);
|
||||
stmt->setString(2, *tabSetting.Name);
|
||||
stmt->setString(3, *tabSetting.Icon);
|
||||
stmt->setString(4, *tabSetting.Description);
|
||||
stmt->setInt32(5, *tabSetting.DepositFlags);
|
||||
trans->Append(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
void Player::_SaveAccountBankItems(CharacterDatabaseTransaction trans)
|
||||
{
|
||||
uint32 bnetAccountId = GetSession()->GetBattlenetAccountId();
|
||||
|
||||
// Delete all account bank item positions ? they will be re-inserted below
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ACCOUNT_BANK_ITEMS_BY_BNET);
|
||||
stmt->setUInt32(0, bnetAccountId);
|
||||
trans->Append(stmt);
|
||||
|
||||
// Re-insert current positions
|
||||
for (uint8 tabIndex = 0; tabIndex < GetAccountBankTabCount(); ++tabIndex)
|
||||
{
|
||||
uint8 bagSlot = ACCOUNT_BANK_SLOT_BAG_START + tabIndex;
|
||||
Bag* bag = GetBagByPos(bagSlot);
|
||||
if (!bag)
|
||||
continue;
|
||||
|
||||
for (uint32 slot = 0; slot < bag->GetBagSize(); ++slot)
|
||||
{
|
||||
Item* item = bag->GetItemByPos(slot);
|
||||
if (!item)
|
||||
continue;
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_ACCOUNT_BANK_ITEM);
|
||||
stmt->setUInt32(0, bnetAccountId);
|
||||
stmt->setUInt8(1, tabIndex);
|
||||
stmt->setUInt8(2, slot);
|
||||
stmt->setUInt64(3, item->GetGUID().GetCounter());
|
||||
trans->Append(stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -848,7 +848,7 @@ enum class ItemSearchLocation
|
||||
Inventory = 0x02,
|
||||
Bank = 0x04,
|
||||
ReagentBank = 0x08,
|
||||
AccountBank = 0x10, // NYI
|
||||
AccountBank = 0x10,
|
||||
|
||||
Default = Equipment | Inventory,
|
||||
Everywhere = Equipment | Inventory | Bank | ReagentBank
|
||||
@@ -1037,6 +1037,9 @@ enum PlayerLoginQueryIndex
|
||||
PLAYER_LOGIN_QUERY_LOAD_HOUSING_ROOMS,
|
||||
PLAYER_LOGIN_QUERY_LOAD_HOUSING_FIXTURES,
|
||||
PLAYER_LOGIN_QUERY_LOAD_HOUSING_CATALOG,
|
||||
PLAYER_LOGIN_QUERY_LOAD_WARBAND_TAXI_MASK,
|
||||
PLAYER_LOGIN_QUERY_LOAD_ACCOUNT_BANK_TAB_SETTINGS,
|
||||
PLAYER_LOGIN_QUERY_LOAD_ACCOUNT_BANK_ITEMS,
|
||||
MAX_PLAYER_LOGIN_QUERY
|
||||
};
|
||||
|
||||
@@ -1482,6 +1485,16 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player>
|
||||
return false;
|
||||
}
|
||||
|
||||
if (flag.HasFlag(ItemSearchLocation::AccountBank))
|
||||
{
|
||||
for (uint8 i = ACCOUNT_BANK_SLOT_BAG_START; i < ACCOUNT_BANK_SLOT_BAG_END; ++i)
|
||||
if (Bag* bag = GetBagByPos(i))
|
||||
for (uint32 j = 0; j < GetBagSize(bag); ++j)
|
||||
if (Item* pItem = GetItemInBag(bag, j))
|
||||
if (callback(pItem) == ItemSearchCallbackResult::Stop)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1501,6 +1514,7 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player>
|
||||
Item* GetUseableItemByPos(uint8 bag, uint8 slot) const;
|
||||
Bag* GetBagByPos(uint8 slot) const;
|
||||
std::vector<Item*> GetCraftingReagentItemsToDeposit();
|
||||
std::vector<Item*> GetWarboundItemsToDeposit();//WowCommunity
|
||||
Item* GetWeaponForAttack(WeaponAttackType attackType, bool useable = false) const;
|
||||
Item* GetShield(bool useable = false) const;
|
||||
Item* GetChildItemByGuid(ObjectGuid guid) const;
|
||||
@@ -1515,7 +1529,7 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player>
|
||||
static bool IsBankPos(uint8 bag, uint8 slot);
|
||||
static bool IsChildEquipmentPos(uint16 pos) { return IsChildEquipmentPos(pos >> 8, pos & 255); }
|
||||
static bool IsChildEquipmentPos(uint8 bag, uint8 slot);
|
||||
static bool IsAccountBankPos(uint16 pos) { return IsBankPos(pos >> 8, pos & 255); }
|
||||
static bool IsAccountBankPos(uint16 pos) { return IsAccountBankPos(pos >> 8, pos & 255); }
|
||||
static bool IsAccountBankPos(uint8 bag, uint8 slot);
|
||||
bool IsValidPos(uint16 pos, bool explicit_pos) const { return IsValidPos(pos >> 8, pos & 255, explicit_pos); }
|
||||
bool IsValidPos(uint8 bag, uint8 slot, bool explicit_pos) const;
|
||||
@@ -1581,6 +1595,7 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player>
|
||||
InventoryResult CanUnequipItems(uint32 item, uint32 count) const;
|
||||
InventoryResult CanUnequipItem(uint16 src, bool swap) const;
|
||||
InventoryResult CanBankItem(uint8 bag, uint8 slot, ItemPosCountVec& dest, Item* pItem, bool swap, bool not_loading = true, bool reagentBankOnly = false) const;
|
||||
InventoryResult CanAccountBankItem(uint8 bag, uint8 slot, ItemPosCountVec& dest, Item* pItem, bool swap) const;
|
||||
InventoryResult CanUseItem(Item* pItem, bool not_loading = true) const;
|
||||
bool HasItemTotemCategory(uint32 TotemCategory) const;
|
||||
InventoryResult CanUseItem(ItemTemplate const* pItem, bool skipRequiredLevelCheck = false) const;
|
||||
@@ -3237,6 +3252,8 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player>
|
||||
void _LoadCUFProfiles(PreparedQueryResult result);
|
||||
void _LoadPlayerData(PreparedQueryResult elementsResult, PreparedQueryResult flagsResult);
|
||||
void _LoadCharacterBankTabSettings(PreparedQueryResult result);
|
||||
void _LoadAccountBankTabSettings(PreparedQueryResult result);
|
||||
void _LoadAccountBankItems(PreparedQueryResult result, uint32 timeDiff);
|
||||
|
||||
/*********************************************************/
|
||||
/*** SAVE SYSTEM ***/
|
||||
@@ -3268,6 +3285,8 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player>
|
||||
void _SaveCUFProfiles(CharacterDatabaseTransaction trans);
|
||||
void _SavePlayerData(CharacterDatabaseTransaction trans);
|
||||
void _SaveCharacterBankTabSettings(CharacterDatabaseTransaction trans) const;
|
||||
void _SaveAccountBankTabSettings(CharacterDatabaseTransaction trans) const;
|
||||
void _SaveAccountBankItems(CharacterDatabaseTransaction trans);
|
||||
|
||||
/*********************************************************/
|
||||
/*** ENVIRONMENTAL SYSTEM ***/
|
||||
|
||||
@@ -269,3 +269,34 @@ FactionTemplateEntry const* PlayerTaxi::GetFlightMasterFactionTemplate() const
|
||||
{
|
||||
return sFactionTemplateStore.LookupEntry(m_flightMasterFactionId);
|
||||
}
|
||||
|
||||
//WowCommunity
|
||||
|
||||
TaxiMask PlayerTaxi::LoadTaxiMaskFromString(std::string const& data)
|
||||
{
|
||||
TaxiMask mask;
|
||||
std::vector<std::string_view> tokens = Trinity::Tokenize(data, ' ', false);
|
||||
for (size_t index = 0; (index < mask.size()) && (index < tokens.size()); ++index)
|
||||
{
|
||||
if (Optional<uint32> val = Trinity::StringTo<uint32>(tokens[index]))
|
||||
mask[index] = sTaxiNodesMask[index] & *val;
|
||||
else
|
||||
mask[index] = 0;
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
void PlayerTaxi::MergeAccountTaxiMask(TaxiMask const& accountMask)
|
||||
{
|
||||
for (TaxiNodesEntry const* node : sTaxiNodesStore)
|
||||
{
|
||||
if (node->GetFlags().HasFlag(TaxiNodeFlags::NotAccountWide))
|
||||
continue;
|
||||
|
||||
uint32 field = uint32((node->ID - 1) / (sizeof(TaxiMask::value_type) * 8));
|
||||
TaxiMask::value_type submask = TaxiMask::value_type(1 << ((node->ID - 1) % (sizeof(TaxiMask::value_type) * 8)));
|
||||
|
||||
if (accountMask[field] & submask)
|
||||
m_taximask[field] |= submask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,11 @@ class TC_GAME_API PlayerTaxi
|
||||
void InitTaxiNodesForLevel(uint32 race, uint32 chrClass, uint8 level);
|
||||
bool LoadTaxiMask(std::string const& data);
|
||||
|
||||
//WowCommunity
|
||||
static TaxiMask LoadTaxiMaskFromString(std::string const& data);
|
||||
void MergeAccountTaxiMask(TaxiMask const& accountMask);
|
||||
//WowCommunity
|
||||
|
||||
bool IsTaximaskNodeKnown(uint32 nodeidx) const
|
||||
{
|
||||
uint32 field = uint32((nodeidx - 1) / (sizeof(TaxiMask::value_type) * 8));
|
||||
|
||||
@@ -37,15 +37,24 @@ void WorldSession::HandleAutoBankItemOpcode(WorldPackets::Bank::AutoBankItem& pa
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet.BankType != BankType::Character)
|
||||
return;
|
||||
|
||||
Item* item = _player->GetItemByPos(packet.Bag, packet.Slot);
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
ItemPosCountVec dest;
|
||||
InventoryResult msg = _player->CanBankItem(NULL_BAG, NULL_SLOT, dest, item, false);
|
||||
InventoryResult msg = EQUIP_ERR_OK;
|
||||
|
||||
if (packet.BankType == BankType::Account)
|
||||
{
|
||||
msg = _player->CanAccountBankItem(NULL_BAG, NULL_SLOT, dest, item, false);
|
||||
}
|
||||
else if (packet.BankType == BankType::Character)
|
||||
{
|
||||
msg = _player->CanBankItem(NULL_BAG, NULL_SLOT, dest, item, false);
|
||||
}
|
||||
else
|
||||
return;
|
||||
|
||||
if (msg != EQUIP_ERR_OK)
|
||||
{
|
||||
_player->SendEquipError(msg, item, nullptr);
|
||||
@@ -116,7 +125,7 @@ void WorldSession::HandleAutoStoreBankItemOpcode(WorldPackets::Bank::AutoStoreBa
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
if (_player->IsBankPos(packet.Bag, packet.Slot)) // moving from bank to inventory
|
||||
if (_player->IsBankPos(packet.Bag, packet.Slot)) // moving from character bank to inventory
|
||||
{
|
||||
ItemPosCountVec dest;
|
||||
InventoryResult msg = _player->CanStoreItem(NULL_BAG, NULL_SLOT, dest, item, false);
|
||||
@@ -129,7 +138,20 @@ void WorldSession::HandleAutoStoreBankItemOpcode(WorldPackets::Bank::AutoStoreBa
|
||||
_player->RemoveItem(packet.Bag, packet.Slot, true);
|
||||
if (Item const* storedItem = _player->StoreItem(dest, item, true))
|
||||
_player->ItemAddedQuestCheck(storedItem->GetEntry(), storedItem->GetCount());
|
||||
}
|
||||
else if (_player->IsAccountBankPos(packet.Bag, packet.Slot)) // moving from account bank to inventory
|
||||
{
|
||||
ItemPosCountVec dest;
|
||||
InventoryResult msg = _player->CanStoreItem(NULL_BAG, NULL_SLOT, dest, item, false);
|
||||
if (msg != EQUIP_ERR_OK)
|
||||
{
|
||||
_player->SendEquipError(msg, item, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
_player->RemoveItem(packet.Bag, packet.Slot, true);
|
||||
if (Item const* storedItem = _player->StoreItem(dest, item, true))
|
||||
_player->ItemAddedQuestCheck(storedItem->GetEntry(), storedItem->GetCount());
|
||||
}
|
||||
else // moving from inventory to bank
|
||||
{
|
||||
@@ -155,7 +177,7 @@ void WorldSession::HandleBuyBankTab(WorldPackets::Bank::BuyBankTab const& buyBan
|
||||
return;
|
||||
}
|
||||
|
||||
if (buyBankTab.BankType != BankType::Character)
|
||||
if (buyBankTab.BankType != BankType::Character && buyBankTab.BankType != BankType::Account)
|
||||
{
|
||||
TC_LOG_DEBUG("network", "WorldSession::HandleBuyBankTab {} - Bank type {} is not supported.",
|
||||
_player->GetGUID(), buyBankTab.BankType);
|
||||
@@ -313,3 +335,42 @@ void WorldSession::SendShowBank(ObjectGuid guid, PlayerInteractionType interacti
|
||||
npcInteraction.Success = true;
|
||||
SendPacket(npcInteraction.Write());
|
||||
}
|
||||
|
||||
void WorldSession::HandleAutoDepositAccountBank(WorldPackets::Bank::AutoDepositAccountBank const& autoDepositAccountBank)
|
||||
{
|
||||
if (!CanUseBank(autoDepositAccountBank.Banker))
|
||||
{
|
||||
TC_LOG_DEBUG("network", "WORLD: HandleAutoDepositAccountBank - {} not found or you can't interact with him.", autoDepositAccountBank.Banker);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_player->GetAccountBankTabCount() == 0)
|
||||
{
|
||||
_player->SendEquipError(EQUIP_ERR_BANK_FULL);
|
||||
return;
|
||||
}
|
||||
|
||||
// Deposit all warbound/BoA items from inventory
|
||||
bool anyDeposited = false;
|
||||
for (Item* item : _player->GetWarboundItemsToDeposit())
|
||||
{
|
||||
ItemPosCountVec dest;
|
||||
InventoryResult msg = _player->CanAccountBankItem(NULL_BAG, NULL_SLOT, dest, item, false);
|
||||
if (msg != EQUIP_ERR_OK)
|
||||
{
|
||||
if (msg != EQUIP_ERR_BANK_FULL || !anyDeposited)
|
||||
_player->SendEquipError(msg, item, nullptr);
|
||||
break;
|
||||
}
|
||||
|
||||
if (dest.size() == 1 && dest[0].pos == item->GetPos())
|
||||
{
|
||||
_player->SendEquipError(EQUIP_ERR_CANT_SWAP, item, nullptr);
|
||||
continue;
|
||||
}
|
||||
|
||||
_player->RemoveItem(item->GetBagSlot(), item->GetSlot(), true);
|
||||
_player->BankItem(dest, item, true);
|
||||
anyDeposited = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,12 +72,14 @@ class LoginQueryHolder : public CharacterDatabaseQueryHolder
|
||||
{
|
||||
private:
|
||||
uint32 m_accountId;
|
||||
uint32 m_battlenetAccountId;
|
||||
ObjectGuid m_guid;
|
||||
public:
|
||||
LoginQueryHolder(uint32 accountId, ObjectGuid guid)
|
||||
: m_accountId(accountId), m_guid(guid) { }
|
||||
LoginQueryHolder(uint32 accountId, uint32 battlenetAccountId, ObjectGuid guid)
|
||||
: m_accountId(accountId), m_battlenetAccountId(battlenetAccountId), m_guid(guid) { }
|
||||
ObjectGuid GetGuid() const { return m_guid; }
|
||||
uint32 GetAccountId() const { return m_accountId; }
|
||||
uint32 GetBattlenetAccountId() const { return m_battlenetAccountId; }
|
||||
bool Initialize();
|
||||
};
|
||||
|
||||
@@ -367,6 +369,14 @@ bool LoginQueryHolder::Initialize()
|
||||
stmt->setUInt64(0, lowGuid);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_BANK_TAB_SETTINGS, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ACCOUNT_BANK_TAB_SETTINGS);
|
||||
stmt->setUInt32(0, m_battlenetAccountId);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_ACCOUNT_BANK_TAB_SETTINGS, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ACCOUNT_BANK_ITEMS);
|
||||
stmt->setUInt32(0, m_battlenetAccountId);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_ACCOUNT_BANK_ITEMS, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_HOUSING);
|
||||
stmt->setUInt64(0, lowGuid);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_HOUSING, stmt);
|
||||
@@ -387,6 +397,10 @@ bool LoginQueryHolder::Initialize()
|
||||
stmt->setUInt64(0, lowGuid);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_HOUSING_CATALOG, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_WARBAND_TAXI_MASK);
|
||||
stmt->setUInt32(0, m_battlenetAccountId);
|
||||
res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_WARBAND_TAXI_MASK, stmt);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -408,9 +422,10 @@ public:
|
||||
SetSize(MAX);
|
||||
}
|
||||
|
||||
bool Initialize(uint32 accountId, bool withDeclinedNames, bool isDeletedCharacters)
|
||||
bool Initialize(uint32 accountId, uint32 battlenetAccountId, bool withDeclinedNames, bool isDeletedCharacters)
|
||||
{
|
||||
_isDeletedCharacters = isDeletedCharacters;
|
||||
_battlenetAccountId = battlenetAccountId;
|
||||
|
||||
constexpr CharacterDatabaseStatements statements[2][3] =
|
||||
{
|
||||
@@ -427,13 +442,26 @@ public:
|
||||
stmt->setUInt32(0, accountId);
|
||||
result &= SetPreparedQuery(CUSTOMIZATIONS, stmt);
|
||||
|
||||
if (!isDeletedCharacters)
|
||||
{
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_WARBAND_GROUPS);
|
||||
stmt->setUInt32(0, battlenetAccountId);
|
||||
result &= SetPreparedQuery(WARBAND_GROUPS, stmt);
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_WARBAND_GROUP_MEMBERS);
|
||||
stmt->setUInt32(0, battlenetAccountId);
|
||||
result &= SetPreparedQuery(WARBAND_GROUP_MEMBERS, stmt);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool IsDeletedCharacters() const { return _isDeletedCharacters; }
|
||||
uint32 GetBattlenetAccountId() const { return _battlenetAccountId; }
|
||||
|
||||
private:
|
||||
bool _isDeletedCharacters = false;
|
||||
uint32 _battlenetAccountId = 0;
|
||||
};
|
||||
|
||||
void WorldSession::HandleCharEnum(CharacterDatabaseQueryHolder const& holder)
|
||||
@@ -514,6 +542,113 @@ void WorldSession::HandleCharEnum(CharacterDatabaseQueryHolder const& holder)
|
||||
charEnum.RaceUnlockData.push_back(raceUnlock);
|
||||
}
|
||||
|
||||
if (!charEnum.IsDeletedCharacters)
|
||||
{
|
||||
EnumCharactersQueryHolder const& enumHolder = static_cast<EnumCharactersQueryHolder const&>(holder);
|
||||
|
||||
// Load existing warband groups from DB
|
||||
std::unordered_map<uint64, WorldPackets::Character::WarbandGroup*> groupsByDbId;
|
||||
if (PreparedQueryResult groupResult = holder.GetPreparedResult(EnumCharactersQueryHolder::WARBAND_GROUPS))
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = groupResult->Fetch();
|
||||
WorldPackets::Character::WarbandGroup& group = charEnum.WarbandGroups.emplace_back();
|
||||
group.GroupID = fields[0].GetUInt64();
|
||||
group.OrderIndex = fields[1].GetUInt8();
|
||||
group.WarbandSceneID = fields[2].GetUInt32();
|
||||
group.Flags = fields[3].GetUInt32();
|
||||
group.ContentSetID = fields[4].GetInt32();
|
||||
group.Name = fields[5].GetString();
|
||||
groupsByDbId[group.GroupID] = &group;
|
||||
} while (groupResult->NextRow());
|
||||
}
|
||||
|
||||
if (PreparedQueryResult memberResult = holder.GetPreparedResult(EnumCharactersQueryHolder::WARBAND_GROUP_MEMBERS))
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = memberResult->Fetch();
|
||||
uint64 groupId = fields[0].GetUInt64();
|
||||
auto it = groupsByDbId.find(groupId);
|
||||
if (it == groupsByDbId.end())
|
||||
continue;
|
||||
|
||||
WorldPackets::Character::WarbandGroupMember member;
|
||||
// fields[1] is memberIndex - used for ordering, implicit from vector position
|
||||
member.Guid = ObjectGuid::Create<HighGuid::Player>(fields[2].GetUInt64());
|
||||
member.WarbandScenePlacementID = fields[3].GetUInt32();
|
||||
member.Type = fields[4].GetInt32();
|
||||
member.ContentSetID = fields[5].GetInt32();
|
||||
it->second->Members.push_back(member);
|
||||
} while (memberResult->NextRow());
|
||||
}
|
||||
|
||||
// If no warband groups exist and we have characters, create a default group
|
||||
if (charEnum.WarbandGroups.empty() && !charEnum.Characters.empty())
|
||||
{
|
||||
// Use the first available warband scene (ID 1 = default "Campfire" scene)
|
||||
uint32 defaultSceneId = 0;
|
||||
for (WarbandSceneEntry const* scene : sWarbandSceneStore)
|
||||
{
|
||||
defaultSceneId = scene->ID;
|
||||
break;
|
||||
}
|
||||
|
||||
if (defaultSceneId != 0)
|
||||
{
|
||||
WorldPackets::Character::WarbandGroup& defaultGroup = charEnum.WarbandGroups.emplace_back();
|
||||
defaultGroup.OrderIndex = 0;
|
||||
defaultGroup.WarbandSceneID = defaultSceneId;
|
||||
defaultGroup.Flags = 0;
|
||||
defaultGroup.ContentSetID = 0;
|
||||
|
||||
// Get valid placement IDs for this scene (only character slots, type 0)
|
||||
std::vector<uint32> characterPlacementIds;
|
||||
if (std::vector<WarbandScenePlacementEntry const*> const* placements = sDB2Manager.GetWarbandScenePlacements(defaultSceneId))
|
||||
{
|
||||
for (WarbandScenePlacementEntry const* placement : *placements)
|
||||
{
|
||||
if (placement->SlotType == 0) // Character slot
|
||||
characterPlacementIds.push_back(placement->ID);
|
||||
}
|
||||
}
|
||||
|
||||
// Assign up to 4 characters (or as many as we have placement slots)
|
||||
uint32 maxMembers = std::min<uint32>(static_cast<uint32>(charEnum.Characters.size()), static_cast<uint32>(characterPlacementIds.size()));
|
||||
for (uint32 i = 0; i < maxMembers; ++i)
|
||||
{
|
||||
WorldPackets::Character::WarbandGroupMember member;
|
||||
member.Guid = charEnum.Characters[i].Basic.Guid;
|
||||
member.WarbandScenePlacementID = characterPlacementIds[i];
|
||||
member.Type = 0; // Character
|
||||
member.ContentSetID = 0;
|
||||
defaultGroup.Members.push_back(member);
|
||||
}
|
||||
|
||||
// Persist the default group asynchronously
|
||||
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
|
||||
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_WARBAND_GROUP);
|
||||
stmt->setUInt64(0, 0); // AUTO_INCREMENT - will be assigned by DB
|
||||
stmt->setUInt32(1, enumHolder.GetBattlenetAccountId());
|
||||
stmt->setUInt8(2, 0); // orderIndex
|
||||
stmt->setUInt32(3, defaultSceneId);
|
||||
stmt->setUInt32(4, 0); // flags
|
||||
stmt->setInt32(5, 0); // contentSetId
|
||||
stmt->setString(6, std::string());
|
||||
trans->Append(stmt);
|
||||
|
||||
CharacterDatabase.CommitTransaction(trans);
|
||||
|
||||
// We need the auto-generated groupId for subsequent member inserts.
|
||||
// Since we can't get LAST_INSERT_ID in an async transaction easily,
|
||||
// we'll set a temporary GroupID of 0 in the packet (client doesn't send it back).
|
||||
// The members will be properly persisted when the client sends CMSG_SETUP_WARBAND_GROUPS.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SendPacket(charEnum.Write());
|
||||
|
||||
if (!charEnum.IsDeletedCharacters)
|
||||
@@ -528,7 +663,7 @@ void WorldSession::HandleCharEnumOpcode(WorldPackets::Character::EnumCharacters&
|
||||
|
||||
/// get all the data necessary for loading all characters (along with their pets) on the account
|
||||
std::shared_ptr<EnumCharactersQueryHolder> holder = std::make_shared<EnumCharactersQueryHolder>();
|
||||
if (!holder->Initialize(GetAccountId(), sWorld->getBoolConfig(CONFIG_DECLINED_NAMES_USED), false))
|
||||
if (!holder->Initialize(GetAccountId(), GetBattlenetAccountId(), sWorld->getBoolConfig(CONFIG_DECLINED_NAMES_USED), false))
|
||||
{
|
||||
HandleCharEnum(*holder);
|
||||
return;
|
||||
@@ -544,7 +679,7 @@ void WorldSession::HandleCharUndeleteEnumOpcode(WorldPackets::Character::EnumCha
|
||||
{
|
||||
/// get all the data necessary for loading all undeleted characters (along with their pets) on the account
|
||||
std::shared_ptr<EnumCharactersQueryHolder> holder = std::make_shared<EnumCharactersQueryHolder>();
|
||||
if (!holder->Initialize(GetAccountId(), sWorld->getBoolConfig(CONFIG_DECLINED_NAMES_USED), true))
|
||||
if (!holder->Initialize(GetAccountId(), GetBattlenetAccountId(), sWorld->getBoolConfig(CONFIG_DECLINED_NAMES_USED), true))
|
||||
{
|
||||
HandleCharEnum(*holder);
|
||||
return;
|
||||
@@ -1148,7 +1283,7 @@ void WorldSession::HandleContinuePlayerLogin()
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<LoginQueryHolder> holder = std::make_shared<LoginQueryHolder>(GetAccountId(), m_playerLoading);
|
||||
std::shared_ptr<LoginQueryHolder> holder = std::make_shared<LoginQueryHolder>(GetAccountId(), GetBattlenetAccountId(), m_playerLoading);
|
||||
if (!holder->Initialize())
|
||||
{
|
||||
m_playerLoading.Clear();
|
||||
|
||||
@@ -163,7 +163,7 @@ void WorldSession::HandleSendMail(WorldPackets::Mail::SendMail& sendMail)
|
||||
if (Item* item = player->GetItemByGuid(att.ItemGUID))
|
||||
{
|
||||
ItemTemplate const* itemProto = item->GetTemplate();
|
||||
if (!itemProto || !itemProto->HasFlag(ITEM_FLAG_IS_BOUND_TO_ACCOUNT))
|
||||
if (!itemProto || !(itemProto->HasFlag(ITEM_FLAG_IS_BOUND_TO_ACCOUNT) || item->IsWarbandBound()))
|
||||
{
|
||||
accountBound = false;
|
||||
break;
|
||||
@@ -215,9 +215,9 @@ void WorldSession::HandleSendMail(WorldPackets::Mail::SendMail& sendMail)
|
||||
return;
|
||||
}
|
||||
|
||||
if (item->IsBoundAccountWide() && item->IsSoulBound() && player->GetSession()->GetAccountId() != receiverAccountId)
|
||||
if ((item->IsBoundAccountWide() || item->IsWarbandBound()) && item->IsSoulBound() && player->GetSession()->GetAccountId() != receiverAccountId)
|
||||
{
|
||||
if (!item->IsBattlenetAccountBound() || !player->GetSession()->GetBattlenetAccountId() || player->GetSession()->GetBattlenetAccountId() != receiverBnetAccountId)
|
||||
if (!(item->IsBattlenetAccountBound() || item->IsWarbandBound()) || !player->GetSession()->GetBattlenetAccountId() || player->GetSession()->GetBattlenetAccountId() != receiverBnetAccountId)
|
||||
{
|
||||
player->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR, EQUIP_ERR_NOT_SAME_ACCOUNT);
|
||||
return;
|
||||
|
||||
@@ -766,6 +766,15 @@ void WorldSession::HandleSetTradeItemOpcode(WorldPackets::Trade::SetTradeItem& s
|
||||
|
||||
my_trade->UpdateClientStateIndex();
|
||||
|
||||
// warbound items cannot be traded to other players
|
||||
if (setTradeItem.TradeSlot != TRADE_SLOT_NONTRADED && item->IsWarbandBound())
|
||||
{
|
||||
info.Status = TRADE_STATUS_NOT_ON_TAPLIST;
|
||||
info.TradeSlot = setTradeItem.TradeSlot;
|
||||
SendTradeStatus(info);
|
||||
return;
|
||||
}
|
||||
|
||||
if (setTradeItem.TradeSlot != TRADE_SLOT_NONTRADED && item->IsBindedNotWith(my_trade->GetTrader()))
|
||||
{
|
||||
info.Status = TRADE_STATUS_NOT_ON_TAPLIST;
|
||||
|
||||
@@ -74,4 +74,10 @@ void UpdateBankTabSettings::Read()
|
||||
_worldPacket >> Tab;
|
||||
_worldPacket >> Settings;
|
||||
}
|
||||
|
||||
void AutoDepositAccountBank::Read()
|
||||
{
|
||||
_worldPacket >> Banker;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -106,6 +106,17 @@ namespace WorldPackets
|
||||
uint8 Tab = 0;
|
||||
BankTabSettings Settings;
|
||||
};
|
||||
|
||||
class AutoDepositAccountBank final : public ClientPacket
|
||||
{
|
||||
public:
|
||||
explicit AutoDepositAccountBank(WorldPacket&& packet) : ClientPacket(CMSG_AUTO_DEPOSIT_ACCOUNT_BANK, std::move(packet)) {}
|
||||
|
||||
void Read() override;
|
||||
|
||||
ObjectGuid Banker;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ void OpcodeTable::InitializeClientOpcodes()
|
||||
DEFINE_HANDLER(CMSG_AUTH_SESSION, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_EarlyProccess);
|
||||
DEFINE_HANDLER(CMSG_AUTOBANK_ITEM, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleAutoBankItemOpcode);
|
||||
DEFINE_HANDLER(CMSG_AUTOSTORE_BANK_ITEM, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleAutoStoreBankItemOpcode);
|
||||
DEFINE_HANDLER(CMSG_AUTO_DEPOSIT_ACCOUNT_BANK, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
|
||||
DEFINE_HANDLER(CMSG_AUTO_DEPOSIT_ACCOUNT_BANK, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAutoDepositAccountBank);
|
||||
DEFINE_HANDLER(CMSG_AUTO_DEPOSIT_CHARACTER_BANK, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAutoDepositCharacterBank);
|
||||
DEFINE_HANDLER(CMSG_AUTO_EQUIP_ITEM, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleAutoEquipItemOpcode);
|
||||
DEFINE_HANDLER(CMSG_AUTO_EQUIP_ITEM_SLOT, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleAutoEquipItemSlotOpcode);
|
||||
|
||||
@@ -183,6 +183,7 @@ namespace WorldPackets
|
||||
class BuyBankTab;
|
||||
class UpdateBankTabSettings;
|
||||
class AutoDepositCharacterBank;
|
||||
class AutoDepositAccountBank;
|
||||
class BankerActivate;
|
||||
}
|
||||
|
||||
@@ -1922,6 +1923,7 @@ class TC_GAME_API WorldSession
|
||||
void HandleBuyBankTab(WorldPackets::Bank::BuyBankTab const& buyBankTab);
|
||||
void HandleUpdateBankTabSettings(WorldPackets::Bank::UpdateBankTabSettings const& updateBankTabSettings);
|
||||
void HandleAutoDepositCharacterBank(WorldPackets::Bank::AutoDepositCharacterBank const& autoDepositCharacterBank);
|
||||
void HandleAutoDepositAccountBank(WorldPackets::Bank::AutoDepositAccountBank const& autoDepositAccountBank);
|
||||
|
||||
// Black Market
|
||||
void HandleBlackMarketOpen(WorldPackets::BlackMarket::BlackMarketOpen& blackMarketOpen);
|
||||
|
||||
Reference in New Issue
Block a user