aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/server/game/Entities/Item/Item.cpp55
-rw-r--r--src/server/game/Entities/Item/Item.h7
-rw-r--r--src/server/game/Entities/Player/Player.cpp173
-rw-r--r--src/server/game/Entities/Player/Player.h13
-rw-r--r--src/server/game/Globals/ObjectMgr.cpp6
-rw-r--r--src/server/game/Groups/Group.cpp16
-rw-r--r--src/server/game/Loot/LootMgr.cpp19
-rw-r--r--src/server/game/Loot/LootMgr.h6
-rw-r--r--src/server/game/Mails/Mail.cpp6
-rw-r--r--src/server/game/Miscellaneous/SharedDefines.h3
-rw-r--r--src/server/game/Server/Protocol/Handlers/ItemHandler.cpp2
-rw-r--r--src/server/game/Server/Protocol/Handlers/LootHandler.cpp5
-rw-r--r--src/server/game/Server/Protocol/Handlers/TradeHandler.cpp23
-rw-r--r--src/server/game/Spells/SpellEffects.cpp4
-rw-r--r--src/server/shared/Database/Implementation/CharacterDatabase.cpp6
-rw-r--r--src/server/shared/Database/Implementation/CharacterDatabase.h6
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,
};