diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/server/game/Entities/Item/Item.cpp | 55 | ||||
-rw-r--r-- | src/server/game/Entities/Item/Item.h | 7 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.cpp | 173 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.h | 13 | ||||
-rw-r--r-- | src/server/game/Globals/ObjectMgr.cpp | 6 | ||||
-rw-r--r-- | src/server/game/Groups/Group.cpp | 16 | ||||
-rw-r--r-- | src/server/game/Loot/LootMgr.cpp | 19 | ||||
-rw-r--r-- | src/server/game/Loot/LootMgr.h | 6 | ||||
-rw-r--r-- | src/server/game/Mails/Mail.cpp | 6 | ||||
-rw-r--r-- | src/server/game/Miscellaneous/SharedDefines.h | 3 | ||||
-rw-r--r-- | src/server/game/Server/Protocol/Handlers/ItemHandler.cpp | 2 | ||||
-rw-r--r-- | src/server/game/Server/Protocol/Handlers/LootHandler.cpp | 5 | ||||
-rw-r--r-- | src/server/game/Server/Protocol/Handlers/TradeHandler.cpp | 23 | ||||
-rw-r--r-- | src/server/game/Spells/SpellEffects.cpp | 4 | ||||
-rw-r--r-- | src/server/shared/Database/Implementation/CharacterDatabase.cpp | 6 | ||||
-rw-r--r-- | src/server/shared/Database/Implementation/CharacterDatabase.h | 6 |
16 files changed, 294 insertions, 56 deletions
diff --git a/src/server/game/Entities/Item/Item.cpp b/src/server/game/Entities/Item/Item.cpp index ffb830bf709..8f330b3bc0b 100644 --- a/src/server/game/Entities/Item/Item.cpp +++ b/src/server/game/Entities/Item/Item.cpp @@ -371,7 +371,9 @@ void Item::SaveToDB(SQLTransaction& trans) }break; case ITEM_REMOVED: { - trans->PAppend("DELETE FROM item_instance WHERE guid = '%u'", guid); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE); + stmt->setUInt32(0, guid); + trans->Append(stmt); if (HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_WRAPPED)) trans->PAppend("DELETE FROM character_gifts WHERE item_guid = '%u'", GetGUIDLow()); delete this; @@ -437,7 +439,7 @@ bool Item::LoadFromDB(uint32 guid, uint64 owner_guid, PreparedQueryResult result need_save = true; } - _LoadIntoDataField(result->GetCString(6), ITEM_FIELD_ENCHANTMENT_1_1, MAX_ENCHANTMENT_SLOT * MAX_ENCHANTMENT_OFFSET); + _LoadIntoDataField(result->GetString(6).c_str(), ITEM_FIELD_ENCHANTMENT_1_1, MAX_ENCHANTMENT_SLOT * MAX_ENCHANTMENT_OFFSET); SetInt32Value(ITEM_FIELD_RANDOM_PROPERTIES_ID, result->GetInt32(7)); // recalculate suffix factor if (GetItemRandomPropertyId() < 0) @@ -472,12 +474,16 @@ bool Item::LoadFromDB(uint32 guid, uint64 owner_guid, PreparedQueryResult result void Item::DeleteFromDB(SQLTransaction& trans) { - trans->PAppend("DELETE FROM item_instance WHERE guid = '%u'", GetGUIDLow()); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE); + stmt->setUInt32(0, GetGUIDLow()); + trans->Append(stmt); } void Item::DeleteFromInventoryDB(SQLTransaction& trans) { - trans->PAppend("DELETE FROM character_inventory WHERE item = '%u'", GetGUIDLow()); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVENTORY_ITEM); + stmt->setUInt32(0, GetGUIDLow()); + trans->Append(stmt); } ItemPrototype const *Item::GetProto() const @@ -736,12 +742,12 @@ bool Item::IsEquipped() const return !IsInBag() && m_slot < EQUIPMENT_SLOT_END; } -bool Item::CanBeTraded(bool mail) const +bool Item::CanBeTraded(bool mail, bool trade) const { if (m_lootGenerated) return false; - if ((!mail || !IsBoundAccountWide()) && IsSoulBound()) + if ((!mail || !IsBoundAccountWide()) && (IsSoulBound() && !HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_BOP_TRADEABLE) || !trade)) return false; if (IsBag() && (Player::IsBagPos(GetPos()) || !((Bag const*)this)->IsEmpty())) @@ -1067,6 +1073,10 @@ bool Item::IsBindedNotWith(Player const* player) const if (GetOwnerGUID() == player->GetGUID()) return false; + if (HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_BOP_TRADEABLE)) + if (allowedGUIDs.find(player->GetGUIDLow()) != allowedGUIDs.end()) + return false; + // BOA item case if (IsBoundAccountWide()) return false; @@ -1171,3 +1181,36 @@ bool Item::IsRefundExpired() { return (GetPlayedTime() > 2*HOUR); } + +void Item::SetSoulboundTradeable(AllowedLooterSet* allowedLooters, Player* currentOwner, bool apply) +{ + if (apply) + { + SetFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_BOP_TRADEABLE); + allowedGUIDs = *allowedLooters; + } + else + { + RemoveFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_BOP_TRADEABLE); + if (allowedGUIDs.empty()) + return; + + allowedGUIDs.clear(); + SetState(ITEM_CHANGED, currentOwner); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_BOP_TRADE); + stmt->setUInt32(0, GetGUIDLow()); + CharacterDatabase.Execute(stmt); + } +} + +bool Item::CheckSoulboundTradeExpire() +{ + // called from owner's update - GetOwner() MUST be valid + if (GetUInt32Value(ITEM_FIELD_CREATE_PLAYED_TIME) + 2*HOUR < GetOwner()->GetTotalPlayedTime()) + { + SetSoulboundTradeable(NULL, GetOwner(), false); + return true; // remove from tradeable list + } + + return false; +} diff --git a/src/server/game/Entities/Item/Item.h b/src/server/game/Entities/Item/Item.h index d238a23a96c..1caef195262 100644 --- a/src/server/game/Entities/Item/Item.h +++ b/src/server/game/Entities/Item/Item.h @@ -254,7 +254,7 @@ class Item : public Object bool IsLocked() const { return !HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_UNLOCKED); } bool IsBag() const { return GetProto()->InventoryType == INVTYPE_BAG; } bool IsBroken() const { return GetUInt32Value(ITEM_FIELD_MAXDURABILITY) > 0 && GetUInt32Value(ITEM_FIELD_DURABILITY) == 0; } - bool CanBeTraded(bool mail = false) const; + bool CanBeTraded(bool mail = false, bool trade = false) const; void SetInTrade(bool b = true) { mb_in_trade = b; } bool IsInTrade() const { return mb_in_trade; } @@ -345,6 +345,10 @@ class Item : public Object uint32 GetPlayedTime(); bool IsRefundExpired(); + // Soulbound trade system + void SetSoulboundTradeable(AllowedLooterSet* allowedLooters, Player* currentOwner, bool apply); + bool CheckSoulboundTradeExpire(); + void BuildUpdate(UpdateDataMapType&); uint32 GetScriptId() const { return GetProto()->ScriptId; } @@ -359,5 +363,6 @@ class Item : public Object uint32 m_refundRecipient; uint32 m_paidMoney; uint32 m_paidExtendedCost; + AllowedLooterSet allowedGUIDs; }; #endif diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 811b9db49bb..215d4819585 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -1301,6 +1301,9 @@ void Player::Update(uint32 p_time) if (now > m_Last_tick) UpdateItemDuration(uint32(now - m_Last_tick)); + if (now > m_Last_tick + IN_MILLISECONDS) + UpdateSoulboundTradeItems(); + if (!m_timedquests.empty()) { QuestSet::iterator iter = m_timedquests.begin(); @@ -4503,7 +4506,9 @@ void Player::DeleteFromDB(uint64 playerguid, uint32 accountId, bool updateRealmC ItemPrototype const* itemProto = sObjectMgr.GetItemPrototype(item_template); if (!itemProto) { - trans->PAppend("DELETE FROM item_instance WHERE guid = '%u'", item_guidlow); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE); + stmt->setUInt32(0, item_guidlow); + trans->Append(stmt); continue; } @@ -11437,7 +11442,7 @@ void Player::RemoveAmmo() } // Return stored item (if stored to stack, it can diff. from pItem). And pItem ca be deleted in this case. -Item* Player::StoreNewItem(ItemPosCountVec const& dest, uint32 item, bool update,int32 randomPropertyId) +Item* Player::StoreNewItem(ItemPosCountVec const& dest, uint32 item, bool update, int32 randomPropertyId, AllowedLooterSet* allowedLooters) { uint32 count = 0; for (ItemPosCountVec::const_iterator itr = dest.begin(); itr != dest.end(); ++itr) @@ -11451,6 +11456,23 @@ Item* Player::StoreNewItem(ItemPosCountVec const& dest, uint32 item, bool update if (randomPropertyId) pItem->SetItemRandomProperties(randomPropertyId); pItem = StoreItem(dest, pItem, update); + + if (allowedLooters && pItem->GetProto()->GetMaxStackSize() == 1 && pItem->IsSoulBound()) + { + pItem->SetSoulboundTradeable(allowedLooters, this, true); + pItem->SetUInt32Value(ITEM_FIELD_CREATE_PLAYED_TIME, GetTotalPlayedTime()); + m_itemSoulboundTradeable.push_back(pItem); + + // save data + std::ostringstream ss; + for (AllowedLooterSet::iterator itr = allowedLooters->begin(); itr != allowedLooters->end(); ++itr) + ss << *itr << " "; + + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_ADD_ITEM_BOP_TRADE); + stmt->setUInt32(0, pItem->GetGUIDLow()); + stmt->setString(1, ss.str()); + CharacterDatabase.Execute(stmt); + } } return pItem; } @@ -11574,6 +11596,8 @@ Item* Player::_StoreItem(uint16 pos, Item *pItem, uint32 count, bool clone, bool pItem->SetOwnerGUID(GetGUID()); // prevent error at next SetState in case trade/mail/buy from vendor pItem->SetNotRefundable(this); pItem->SetState(ITEM_REMOVED, this); + pItem->SetSoulboundTradeable(NULL, this, false); + RemoveTradeableItem(pItem->GetGUIDLow()); } // AddItemDurations(pItem2); - pItem2 already have duration listed for player @@ -11691,6 +11715,8 @@ Item* Player::EquipItem(uint16 pos, Item *pItem, bool update) pItem->SetOwnerGUID(GetGUID()); // prevent error at next SetState in case trade/mail/buy from vendor pItem->SetNotRefundable(this); + pItem->SetSoulboundTradeable(NULL, this, false); + RemoveTradeableItem(pItem->GetGUIDLow()); pItem->SetState(ITEM_REMOVED, this); pItem2->SetState(ITEM_CHANGED, this); @@ -11862,6 +11888,15 @@ void Player::MoveItemFromInventory(uint8 bag, uint8 slot, bool update) it->RemoveFromWorld(); it->DestroyForPlayer(this); } + for (ItemDurationList::iterator itr = m_itemSoulboundTradeable.begin(); itr != m_itemSoulboundTradeable.end();) + { + if ((*itr)->GetGUID() == it->GetGUID()) + { + m_itemSoulboundTradeable.erase(itr++); + break; + } + ++itr; + } } } @@ -11886,6 +11921,9 @@ void Player::MoveItemToInventory(ItemPosCountVec const& dest, Item* pItem, bool // in case trade we already have item in other player inventory pLastItem->SetState(in_characterInventoryDB ? ITEM_CHANGED : ITEM_NEW, this); } + + if (pLastItem->HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_BOP_TRADEABLE)) + m_itemSoulboundTradeable.push_back(pLastItem); } void Player::DestroyItem(uint8 bag, uint8 slot, bool update) @@ -11909,6 +11947,8 @@ void Player::DestroyItem(uint8 bag, uint8 slot, bool update) RemoveItemDurations(pItem); pItem->SetNotRefundable(this); + pItem->SetSoulboundTradeable(NULL, this, false); + RemoveTradeableItem(pItem->GetGUIDLow()); ItemRemovedQuestCheck(pItem->GetEntry(), pItem->GetCount()); @@ -12848,6 +12888,46 @@ void Player::TradeCancel(bool sendback) } } +void Player::UpdateSoulboundTradeItems() +{ + if (m_itemSoulboundTradeable.empty()) + return; + + // also checks for garbage data + for (ItemDurationList::iterator itr = m_itemSoulboundTradeable.begin(); itr != m_itemSoulboundTradeable.end();) + { + if (!*itr) + { + itr = m_itemSoulboundTradeable.erase(itr++); + continue; + } + if ((*itr)->GetOwnerGUID() != GetGUID()) + { + itr = m_itemSoulboundTradeable.erase(itr++); + continue; + } + if ((*itr)->CheckSoulboundTradeExpire()) + { + itr = m_itemSoulboundTradeable.erase(itr++); + continue; + } + ++itr; + } +} + +void Player::RemoveTradeableItem(uint32 lowGuid) +{ + for (ItemDurationList::iterator itr = m_itemSoulboundTradeable.begin(); itr != m_itemSoulboundTradeable.end();) + { + if ((*itr)->GetGUIDLow() == lowGuid) + { + m_itemSoulboundTradeable.erase(itr++); + break; + } + ++itr; + } +} + void Player::UpdateItemDuration(uint32 time, bool realtimeonly) { if (m_itemDuration.empty()) @@ -15913,8 +15993,8 @@ bool Player::LoadFromDB(uint32 guid, SQLQueryHolder *holder) SetUInt32Value(UNIT_FIELD_LEVEL, result->GetUInt8(6)); SetUInt32Value(PLAYER_XP, result->GetUInt32(7)); - _LoadIntoDataField(result->GetCString(61), PLAYER_EXPLORED_ZONES_1, PLAYER_EXPLORED_ZONES_SIZE); - _LoadIntoDataField(result->GetCString(64), PLAYER__FIELD_KNOWN_TITLES, KNOWN_TITLES_SIZE*2); + _LoadIntoDataField(result->GetString(61).c_str(), PLAYER_EXPLORED_ZONES_1, PLAYER_EXPLORED_ZONES_SIZE); + _LoadIntoDataField(result->GetString(64).c_str(), PLAYER__FIELD_KNOWN_TITLES, KNOWN_TITLES_SIZE*2); SetFloatValue(UNIT_FIELD_BOUNDINGRADIUS, DEFAULT_WORLD_OBJECT_SIZE); SetFloatValue(UNIT_FIELD_COMBATREACH, 1.5f); @@ -16261,7 +16341,7 @@ bool Player::LoadFromDB(uint32 guid, SQLQueryHolder *holder) if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GM)) SetUInt32Value(PLAYER_FLAGS, 0 | old_safe_flags); - m_taxi.LoadTaxiMask(result->GetCString(17)); // must be before InitTaxiNodesForLevel + m_taxi.LoadTaxiMask(result->GetString(17).c_str()); // must be before InitTaxiNodesForLevel uint32 extraflags = result->GetUInt32(31); @@ -16707,8 +16787,12 @@ void Player::_LoadInventory(PreparedQueryResult result, uint32 timediff) if (!proto) { - trans->PAppend("DELETE FROM character_inventory WHERE item = '%u'", item_guid); - trans->PAppend("DELETE FROM item_instance WHERE guid = '%u'", item_guid); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVENTORY_ITEM); + stmt->setUInt32(0, item_guid); + trans->Append(stmt); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE); + stmt->setUInt32(0, item_guid); + trans->Append(stmt); sLog.outError("Player::_LoadInventory: Player %s has an unknown item (id: #%u) in inventory, deleted.", GetName(),item_id); continue; } @@ -16718,7 +16802,9 @@ void Player::_LoadInventory(PreparedQueryResult result, uint32 timediff) if (!item->LoadFromDB(item_guid, GetGUID(), result, item_id)) { sLog.outError("Player::_LoadInventory: Player %s has broken item (id: #%u) in inventory, deleted.", GetName(),item_id); - CharacterDatabase.PExecute("DELETE FROM character_inventory WHERE item = '%u'", item_guid); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVENTORY_ITEM); + stmt->setUInt32(0, item_guid); + trans->Append(stmt); item->FSetState(ITEM_REMOVED); item->SaveToDB(trans); // it also deletes item object ! continue; @@ -16727,7 +16813,9 @@ void Player::_LoadInventory(PreparedQueryResult result, uint32 timediff) // not allow have in alive state item limited to another map/zone if (isAlive() && item->IsLimitedToAnotherMapOrZone(GetMapId(), zone)) { - CharacterDatabase.PExecute("DELETE FROM character_inventory WHERE item = '%u'", item_guid); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVENTORY_ITEM); + stmt->setUInt32(0, item_guid); + trans->Append(stmt); item->FSetState(ITEM_REMOVED); item->SaveToDB(trans); // it also deletes item object ! continue; @@ -16736,7 +16824,9 @@ void Player::_LoadInventory(PreparedQueryResult result, uint32 timediff) // "Conjured items disappear if you are logged out for more than 15 minutes" if (timediff > 15*MINUTE && proto->Flags & ITEM_PROTO_FLAG_CONJURED) { - CharacterDatabase.PExecute("DELETE FROM character_inventory WHERE item = '%u'", item_guid); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVENTORY_ITEM); + stmt->setUInt32(0, item_guid); + trans->Append(stmt); item->FSetState(ITEM_REMOVED); item->SaveToDB(trans); // it also deletes item object ! continue; @@ -16752,26 +16842,45 @@ void Player::_LoadInventory(PreparedQueryResult result, uint32 timediff) } else { - QueryResult result2 = CharacterDatabase.PQuery( - "SELECT player_guid,paidMoney,paidExtendedCost FROM `item_refund_instance` WHERE item_guid = '%u' AND player_guid = '%u' LIMIT 1", - item->GetGUIDLow(), GetGUIDLow()); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_LOAD_ITEM_REFUNDS); + stmt->setUInt32(0, item->GetGUIDLow()); + stmt->setUInt32(1, GetGUIDLow()); + PreparedQueryResult result2 = CharacterDatabase.Query(stmt); if (!result2) { - sLog.outDebug("Item::LoadFromDB, " - "Item GUID: %u has field flags & ITEM_FLAGS_REFUNDABLE but has no data in item_refund_instance, removing flag.", - item->GetGUIDLow()); + sLog.outDebug("Item::LoadFromDB, Item GUID: %u has field flags & ITEM_FLAGS_REFUNDABLE but has no data in item_refund_instance, removing flag.", item->GetGUIDLow()); item->RemoveFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_REFUNDABLE); } else { - Field* fields2 = result2->Fetch(); - item->SetRefundRecipient(fields2[0].GetUInt32()); - item->SetPaidMoney(fields2[1].GetUInt32()); - item->SetPaidExtendedCost(fields2[2].GetUInt32()); + item->SetRefundRecipient(result2->GetUInt32(0)); + item->SetPaidMoney(result2->GetUInt32(1)); + item->SetPaidExtendedCost(result2->GetUInt32(2)); AddRefundReference(item->GetGUIDLow()); } } } + else if (item->HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_BOP_TRADEABLE)) + { + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_LOAD_ITEM_BOP_TRADE); + stmt->setUInt32(0, item->GetGUIDLow()); + PreparedQueryResult result2 = CharacterDatabase.Query(stmt); + if (!result2) + { + sLog.outDebug("Item::LoadFromDB, Item GUID: %u has flag ITEM_FLAG_BOP_TRADEABLE but has no data in item_soulbound_trade_data, removing flag.", item->GetGUIDLow()); + item->RemoveFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_BOP_TRADEABLE); + } + else + { + std::string strGUID = result2->GetString(0); + Tokens GUIDlist = StrSplit(strGUID, " "); + AllowedLooterSet looters; + for (Tokens::iterator itr = GUIDlist.begin(); itr != GUIDlist.end(); ++itr) + looters.insert(atol((*itr).c_str())); + item->SetSoulboundTradeable(&looters, this, true); + m_itemSoulboundTradeable.push_back(item); + } + } bool success = true; @@ -16840,7 +16949,9 @@ void Player::_LoadInventory(PreparedQueryResult result, uint32 timediff) else { sLog.outError("Player::_LoadInventory: Player %s has item (GUID: %u Entry: %u) can't be loaded to inventory (Bag GUID: %u Slot: %u) by some reason, will send by mail.", GetName(),item_guid, item_id, bag_guid, slot); - CharacterDatabase.PExecute("DELETE FROM character_inventory WHERE item = '%u'", item_guid); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVENTORY_ITEM); + stmt->setUInt32(0, item_guid); + trans->Append(stmt); problematicItems.push_back(item); } } while (result->NextRow()); @@ -16895,7 +17006,9 @@ void Player::_LoadMailedItems(Mail *mail) { sLog.outError("Player %u has unknown item_template (ProtoType) in mailed items(GUID: %u template: %u) in mail (%u), deleted.", GetGUIDLow(), item_guid_low, item_template,mail->messageID); trans->PAppend("DELETE FROM mail_items WHERE item_guid = '%u'", item_guid_low); - trans->PAppend("DELETE FROM item_instance WHERE guid = '%u'", item_guid_low); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE); + stmt->setUInt32(0, item_guid_low); + trans->Append(stmt); continue; } @@ -17870,7 +17983,9 @@ void Player::_SaveInventory(SQLTransaction& trans) if (!item || item->GetState() == ITEM_NEW) continue; trans->PAppend("DELETE FROM character_inventory WHERE item = '%u'", item->GetGUIDLow()); - trans->PAppend("DELETE FROM item_instance WHERE guid = '%u'", item->GetGUIDLow()); + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE); + stmt->setUInt32(0, item->GetGUIDLow()); + trans->Append(stmt); m_items[i]->FSetState(ITEM_NEW); } @@ -17988,8 +18103,15 @@ void Player::_SaveMail(SQLTransaction& trans) else if (m->state == MAIL_STATE_DELETED) { if (m->HasItems()) + { + PreparedStatement* stmt = NULL; for (std::vector<MailItemInfo>::iterator itr2 = m->items.begin(); itr2 != m->items.end(); ++itr2) - trans->PAppend("DELETE FROM item_instance WHERE guid = '%u'", itr2->item_guid); + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE); + stmt->setUInt32(0, itr2->item_guid); + trans->Append(stmt); + } + } trans->PAppend("DELETE FROM mail WHERE id = '%u'", m->messageID); trans->PAppend("DELETE FROM mail_items WHERE mail_id = '%u'", m->messageID); } @@ -22721,7 +22843,8 @@ void Player::StoreLootItem(uint8 lootSlot, Loot* loot) uint8 msg = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, item->itemid, item->count); if (msg == EQUIP_ERR_OK) { - Item * newitem = StoreNewItem(dest, item->itemid, true, item->randomPropertyId); + AllowedLooterSet* looters = item->GetAllowedLooters(); + Item * newitem = StoreNewItem(dest, item->itemid, true, item->randomPropertyId, (looters->size() > 1) ? looters : NULL); if (qitem) { diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index a49a22c79ca..c6b9bb4b479 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1187,7 +1187,7 @@ class Player : public Unit, public GridObject<Player> bool HasItemTotemCategory(uint32 TotemCategory) const; uint8 CanUseItem(ItemPrototype const *pItem) const; uint8 CanUseAmmo(uint32 item) const; - Item* StoreNewItem(ItemPosCountVec const& pos, uint32 item, bool update,int32 randomPropertyId = 0); + Item* StoreNewItem(ItemPosCountVec const& pos, uint32 item, bool update, int32 randomPropertyId = 0, AllowedLooterSet* allowedLooters = NULL); Item* StoreItem(ItemPosCountVec const& pos, Item *pItem, bool update); Item* EquipNewItem(uint16 pos, uint32 item, bool update); Item* EquipItem(uint16 pos, Item *pItem, bool update); @@ -1261,13 +1261,15 @@ class Player : public Unit, public GridObject<Player> void TradeCancel(bool sendback); void UpdateEnchantTime(uint32 time); - void UpdateItemDuration(uint32 time, bool realtimeonly=false); + void UpdateSoulboundTradeItems(); + void RemoveTradeableItem(uint32 guid); + void UpdateItemDuration(uint32 time, bool realtimeonly = false); void AddEnchantmentDurations(Item *item); void RemoveEnchantmentDurations(Item *item); void RemoveArenaEnchantments(EnchantmentSlot slot); - void AddEnchantmentDuration(Item *item,EnchantmentSlot slot,uint32 duration); - void ApplyEnchantment(Item *item,EnchantmentSlot slot,bool apply, bool apply_dur = true, bool ignore_condition = false); - void ApplyEnchantment(Item *item,bool apply); + void AddEnchantmentDuration(Item *item, EnchantmentSlot slot, uint32 duration); + void ApplyEnchantment(Item *item, EnchantmentSlot slot, bool apply, bool apply_dur = true, bool ignore_condition = false); + void ApplyEnchantment(Item *item, bool apply); void UpdateSkillEnchantments(uint16 skill_id, uint16 curr_value, uint16 new_value); void SendEnchantmentDurations(); void BuildEnchantmentsInfoData(WorldPacket *data); @@ -2535,6 +2537,7 @@ class Player : public Unit, public GridObject<Player> EnchantDurationList m_enchantDuration; ItemDurationList m_itemDuration; + ItemDurationList m_itemSoulboundTradeable; void ResetTimeSync(); void SendTimeSync(); diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index 34c122109e1..c67c3e214e4 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -5553,7 +5553,11 @@ void ObjectMgr::ReturnOrDeleteOldMails(bool serverUp) { // mail open and then not returned for (std::vector<MailItemInfo>::iterator itr2 = m->items.begin(); itr2 != m->items.end(); ++itr2) - CharacterDatabase.PExecute("DELETE FROM item_instance WHERE guid = '%u'", itr2->item_guid); + { + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE); + stmt->setUInt32(0, itr2->item_guid); + CharacterDatabase.Execute(stmt); + } } else { diff --git a/src/server/game/Groups/Group.cpp b/src/server/game/Groups/Group.cpp index 8877839e2af..415a406635d 100644 --- a/src/server/game/Groups/Group.cpp +++ b/src/server/game/Groups/Group.cpp @@ -665,6 +665,7 @@ void Group::GroupLoot(Loot *loot, WorldObject* pLootedObject) { if (member->IsWithinDistInMap(pLootedObject,sWorld.getFloatConfig(CONFIG_GROUP_XP_DISTANCE),false)) { + r->totalPlayersRolling++; if (member->GetPassOnGroupLoot()) @@ -674,9 +675,7 @@ void Group::GroupLoot(Loot *loot, WorldObject* pLootedObject) // can't broadcast the pass now. need to wait until all rolling players are known. } else - { r->playerVote[member->GetGUID()] = NOT_EMITED_YET; - } } } } @@ -723,7 +722,7 @@ void Group::GroupLoot(Loot *loot, WorldObject* pLootedObject) delete r; } else - i->is_underthreshold=1; + i->is_underthreshold = true; } } @@ -750,7 +749,8 @@ void Group::NeedBeforeGreed(Loot *loot, WorldObject* pLootedObject) if (!playerToRoll || !playerToRoll->GetSession()) continue; - if (playerToRoll->CanUseItem(item) == EQUIP_ERR_OK && i->AllowedForPlayer(playerToRoll)) + bool allowedForPlayer = i->AllowedForPlayer(playerToRoll); + if (playerToRoll->CanUseItem(item) == EQUIP_ERR_OK && allowedForPlayer) { if (playerToRoll->IsWithinDistInMap(pLootedObject,sWorld.getFloatConfig(CONFIG_GROUP_XP_DISTANCE),false)) { @@ -808,7 +808,7 @@ void Group::NeedBeforeGreed(Loot *loot, WorldObject* pLootedObject) delete r; } else - i->is_underthreshold=1; + i->is_underthreshold = true; } } @@ -949,7 +949,8 @@ void Group::CountTheRoll(Rolls::iterator rollI, uint32 NumberOfPlayers) item->is_looted = true; roll->getLoot()->NotifyItemRemoved(roll->itemSlot); roll->getLoot()->unlootedCount--; - player->StoreNewItem(dest, roll->itemid, true, item->randomPropertyId); + AllowedLooterSet* looters = item->GetAllowedLooters(); + player->StoreNewItem(dest, roll->itemid, true, item->randomPropertyId, (looters->size() > 1) ? looters : NULL); } else { @@ -1001,7 +1002,8 @@ void Group::CountTheRoll(Rolls::iterator rollI, uint32 NumberOfPlayers) item->is_looted = true; roll->getLoot()->NotifyItemRemoved(roll->itemSlot); roll->getLoot()->unlootedCount--; - player->StoreNewItem(dest, roll->itemid, true, item->randomPropertyId); + AllowedLooterSet* looters = item->GetAllowedLooters(); + player->StoreNewItem(dest, roll->itemid, true, item->randomPropertyId, (looters->size() > 1) ? looters : NULL); } else { diff --git a/src/server/game/Loot/LootMgr.cpp b/src/server/game/Loot/LootMgr.cpp index d7a1791a7be..d972dc5f301 100644 --- a/src/server/game/Loot/LootMgr.cpp +++ b/src/server/game/Loot/LootMgr.cpp @@ -381,6 +381,11 @@ bool LootItem::AllowedForPlayer(Player const * player) const return true; } +void LootItem::AddAllowedLooter(const Player *player) +{ + allowedGUIDs.insert(player->GetGUIDLow()); +} + // // --------- Loot --------- // @@ -557,13 +562,17 @@ QuestItemList* Loot::FillNonQuestNonFFAConditionalLoot(Player* player) for (uint8 i = 0; i < items.size(); ++i) { LootItem &item = items[i]; - if (!item.is_looted && !item.freeforall && !item.conditions.empty() && item.AllowedForPlayer(player)) + if (!item.is_looted && !item.freeforall && item.AllowedForPlayer(player)) { - ql->push_back(QuestItem(i)); - if (!item.is_counted) + item.AddAllowedLooter(player); + if (!item.conditions.empty()) { - ++unlootedCount; - item.is_counted = true; + ql->push_back(QuestItem(i)); + if (!item.is_counted) + { + ++unlootedCount; + item.is_counted = true; + } } } } diff --git a/src/server/game/Loot/LootMgr.h b/src/server/game/Loot/LootMgr.h index b8b415438e7..7e4cdeff5c8 100644 --- a/src/server/game/Loot/LootMgr.h +++ b/src/server/game/Loot/LootMgr.h @@ -124,12 +124,15 @@ struct LootStoreItem // Checks correctness of values }; +typedef std::set<uint32> AllowedLooterSet; + struct LootItem { uint32 itemid; uint32 randomSuffix; int32 randomPropertyId; ConditionList conditions; // additional loot condition + AllowedLooterSet allowedGUIDs; uint8 count : 8; bool is_looted : 1; bool is_blocked : 1; @@ -144,6 +147,9 @@ struct LootItem // Basic checks for player/item compatibility - if false no chance to see the item in the loot bool AllowedForPlayer(Player const * player) const; + + void AddAllowedLooter(Player const* player); + AllowedLooterSet* GetAllowedLooters() { return &allowedGUIDs; } }; struct QuestItem diff --git a/src/server/game/Mails/Mail.cpp b/src/server/game/Mails/Mail.cpp index 3d17b867d04..8913654f3dd 100644 --- a/src/server/game/Mails/Mail.cpp +++ b/src/server/game/Mails/Mail.cpp @@ -110,7 +110,11 @@ void MailDraft::deleteIncludedItems(SQLTransaction& trans, bool inDB /*= false*/ Item* item = mailItemIter->second; if (inDB) - trans->PAppend("DELETE FROM item_instance WHERE guid='%u'", item->GetGUIDLow()); + { + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE); + stmt->setUInt32(0, item->GetGUIDLow()); + trans->Append(stmt); + } delete item; } diff --git a/src/server/game/Miscellaneous/SharedDefines.h b/src/server/game/Miscellaneous/SharedDefines.h index 1ab7b6dc36e..105a4616e4b 100644 --- a/src/server/game/Miscellaneous/SharedDefines.h +++ b/src/server/game/Miscellaneous/SharedDefines.h @@ -2814,7 +2814,8 @@ enum TradeStatus TRADE_STATUS_YOU_LOGOUT = 19, TRADE_STATUS_TARGET_LOGOUT = 20, TRADE_STATUS_TRIAL_ACCOUNT = 21, // Trial accounts can not perform that action - TRADE_STATUS_ONLY_CONJURED = 22 // You can only trade conjured items... (cross realm BG related). + TRADE_STATUS_ONLY_CONJURED = 22, // You can only trade conjured items... (cross realm BG related). + TRADE_STATUS_NOT_ELIGIBLE = 23 // Related to trading soulbound loot items }; enum XPColorChar diff --git a/src/server/game/Server/Protocol/Handlers/ItemHandler.cpp b/src/server/game/Server/Protocol/Handlers/ItemHandler.cpp index 322f6483236..79c44b9813f 100644 --- a/src/server/game/Server/Protocol/Handlers/ItemHandler.cpp +++ b/src/server/game/Server/Protocol/Handlers/ItemHandler.cpp @@ -1338,6 +1338,8 @@ void WorldSession::HandleSocketOpcode(WorldPacket& recv_data) } _player->ToggleMetaGemsActive(slot, true); //turn on all metagems (except for target item) + + itemTarget->SetSoulboundTradeable(NULL, _player, false); // clear tradeable flag } void WorldSession::HandleCancelTempEnchantmentOpcode(WorldPacket& recv_data) diff --git a/src/server/game/Server/Protocol/Handlers/LootHandler.cpp b/src/server/game/Server/Protocol/Handlers/LootHandler.cpp index 1baaa8faa84..e4b9ec4997f 100644 --- a/src/server/game/Server/Protocol/Handlers/LootHandler.cpp +++ b/src/server/game/Server/Protocol/Handlers/LootHandler.cpp @@ -473,8 +473,11 @@ void WorldSession::HandleLootMasterGiveOpcode(WorldPacket & recv_data) return; } + // list of players allowed to receive this item in trade + AllowedLooterSet* looters = item.GetAllowedLooters(); + // not move item from loot to target inventory - Item * newitem = target->StoreNewItem(dest, item.itemid, true, item.randomPropertyId); + Item * newitem = target->StoreNewItem(dest, item.itemid, true, item.randomPropertyId, (looters->size() > 1) ? looters : NULL); target->SendNewItem(newitem, uint32(item.count), false, false, true); target->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_ITEM, item.itemid, item.count); target->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_LOOT_TYPE, pLoot->loot_type, item.count); diff --git a/src/server/game/Server/Protocol/Handlers/TradeHandler.cpp b/src/server/game/Server/Protocol/Handlers/TradeHandler.cpp index 4ae4a3dc76b..aae2becbce4 100644 --- a/src/server/game/Server/Protocol/Handlers/TradeHandler.cpp +++ b/src/server/game/Server/Protocol/Handlers/TradeHandler.cpp @@ -55,6 +55,7 @@ void WorldSession::SendTradeStatus(TradeStatus status) data << uint32(0); break; case TRADE_STATUS_ONLY_CONJURED: + case TRADE_STATUS_NOT_ELIGIBLE: data.Initialize(SMSG_TRADE_STATUS, 4+1); data << uint32(status); data << uint8(0); @@ -162,6 +163,8 @@ void WorldSession::moveItems(Item* myItems[], Item* hisItems[]) // store trader->MoveItemToInventory(traderDst, myItems[i], true, true); + if (myItems[i]->HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_BOP_TRADEABLE)) + myItems[i]->SetUInt32Value(ITEM_FIELD_CREATE_PLAYED_TIME, trader->GetTotalPlayedTime()-(_player->GetTotalPlayedTime()-myItems[i]->GetUInt32Value(ITEM_FIELD_CREATE_PLAYED_TIME))); } if (hisItems[i]) { @@ -177,6 +180,8 @@ void WorldSession::moveItems(Item* myItems[], Item* hisItems[]) // store _player->MoveItemToInventory(playerDst, hisItems[i], true, true); + if (hisItems[i]->HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_BOP_TRADEABLE)) + hisItems[i]->SetUInt32Value(ITEM_FIELD_CREATE_PLAYED_TIME, _player->GetTotalPlayedTime()-(trader->GetTotalPlayedTime()-hisItems[i]->GetUInt32Value(ITEM_FIELD_CREATE_PLAYED_TIME))); } } else @@ -291,20 +296,32 @@ void WorldSession::HandleAcceptTradeOpcode(WorldPacket& /*recvPacket*/) { if (Item* item = my_trade->GetItem(TradeSlots(i))) { - if (!item->CanBeTraded()) + if (!item->CanBeTraded(false, true)) { SendTradeStatus(TRADE_STATUS_TRADE_CANCELED); return; } + if (item->IsBindedNotWith(trader)) + { + SendTradeStatus(TRADE_STATUS_NOT_ELIGIBLE); + SendTradeStatus(TRADE_STATUS_CLOSE_WINDOW/*TRADE_STATUS_TRADE_CANCELED*/); + return; + } } if (Item* item = his_trade->GetItem(TradeSlots(i))) { - if (!item->CanBeTraded()) + if (!item->CanBeTraded(false, true)) { SendTradeStatus(TRADE_STATUS_TRADE_CANCELED); return; } + //if (item->IsBindedNotWith(_player)) // dont mark as invalid when his item isnt good (not exploitable because if item is invalid trade will fail anyway later on the same check) + //{ + // SendTradeStatus(TRADE_STATUS_NOT_ELIGIBLE); + // his_trade->SetAccepted(false, true); + // return; + //} } } @@ -674,7 +691,7 @@ void WorldSession::HandleSetTradeItemOpcode(WorldPacket& recvPacket) // check cheating, can't fail with correct client operations Item* item = _player->GetItemByPos(bag, slot); - if (!item || (tradeSlot != TRADE_SLOT_NONTRADED && !item->CanBeTraded())) + if (!item || (tradeSlot != TRADE_SLOT_NONTRADED && !item->CanBeTraded(false, true))) { SendTradeStatus(TRADE_STATUS_TRADE_CANCELED); return; diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index 166066f1bb9..af94a8ec9a5 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -3443,6 +3443,8 @@ void Spell::EffectEnchantItemPerm(SpellEffIndex effIndex) // add new enchanting if equipped item_owner->ApplyEnchantment(itemTarget,PERM_ENCHANTMENT_SLOT,true); + + itemTarget->SetSoulboundTradeable(NULL, item_owner, false); } } @@ -3502,6 +3504,8 @@ void Spell::EffectEnchantItemPrismatic(SpellEffIndex effIndex) // add new enchanting if equipped item_owner->ApplyEnchantment(itemTarget,PRISMATIC_ENCHANTMENT_SLOT,true); + + itemTarget->SetSoulboundTradeable(NULL, item_owner, false); } void Spell::EffectEnchantItemTmp(SpellEffIndex effIndex) diff --git a/src/server/shared/Database/Implementation/CharacterDatabase.cpp b/src/server/shared/Database/Implementation/CharacterDatabase.cpp index 0d792848197..0ba4d88d2d2 100644 --- a/src/server/shared/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/shared/Database/Implementation/CharacterDatabase.cpp @@ -89,6 +89,12 @@ bool CharacterDatabaseConnection::Open(const std::string& infoString) PrepareStatement(CHAR_LOAD_PLAYER_MAILITEMS, "SELECT creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, playedTime, text, item_guid, item_template, owner_guid FROM mail_items JOIN item_instance ON item_guid = guid WHERE mail_id = ?"); PrepareStatement(CHAR_LOAD_AUCTION_ITEMS, "SELECT creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, playedTime, text, itemguid, item_template FROM auctionhouse JOIN item_instance ON itemguid = guid"); PrepareStatement(CHAR_LOAD_GUILD_BANK_ITEMS, "SELECT creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, playedTime, text, TabId, SlotId, item_guid, item_entry, guildid FROM guild_bank_item JOIN item_instance ON item_guid = guid"); + PrepareStatement(CHAR_LOAD_ITEM_REFUNDS, "SELECT player_guid, paidMoney, paidExtendedCost FROM item_refund_instance WHERE item_guid = ? AND player_guid = ? LIMIT 1"); + PrepareStatement(CHAR_LOAD_ITEM_BOP_TRADE, "SELECT allowedPlayers FROM item_soulbound_trade_data WHERE itemGuid = ? LIMIT 1"); + PrepareStatement(CHAR_DEL_ITEM_BOP_TRADE, "DELETE FROM item_soulbound_trade_data WHERE itemGuid = ? LIMIT 1"); + PrepareStatement(CHAR_ADD_ITEM_BOP_TRADE, "INSERT INTO item_soulbound_trade_data VALUES (?, ?)"); + PrepareStatement(CHAR_DEL_INVENTORY_ITEM, "DELETE FROM character_inventory WHERE item = ?"); + PrepareStatement(CHAR_DEL_ITEM_INSTANCE, "DELETE FROM item_instance WHERE guid = ?"); return true; } diff --git a/src/server/shared/Database/Implementation/CharacterDatabase.h b/src/server/shared/Database/Implementation/CharacterDatabase.h index 3df4c9c0d35..c088a44ff9c 100644 --- a/src/server/shared/Database/Implementation/CharacterDatabase.h +++ b/src/server/shared/Database/Implementation/CharacterDatabase.h @@ -95,6 +95,12 @@ enum CharacterDatabaseStatements CHAR_LOAD_PLAYER_MAILITEMS, CHAR_LOAD_AUCTION_ITEMS, CHAR_LOAD_GUILD_BANK_ITEMS, + CHAR_LOAD_ITEM_REFUNDS, + CHAR_LOAD_ITEM_BOP_TRADE, + CHAR_DEL_ITEM_BOP_TRADE, + CHAR_ADD_ITEM_BOP_TRADE, + CHAR_DEL_INVENTORY_ITEM, + CHAR_DEL_ITEM_INSTANCE, MAX_CHARACTERDATABASE_STATEMENTS, }; |