diff options
18 files changed, 323 insertions, 56 deletions
diff --git a/sql/base/characters_database.sql b/sql/base/characters_database.sql index ede2e59e225..acccb19513a 100644 --- a/sql/base/characters_database.sql +++ b/sql/base/characters_database.sql @@ -1792,6 +1792,29 @@ LOCK TABLES `item_refund_instance` WRITE;  UNLOCK TABLES;  -- +-- Table structure for table `item_soulbound_trade_data` +-- + +DROP TABLE IF EXISTS `item_soulbound_trade_data`; +/*!40101 SET @saved_cs_client     = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `item_soulbound_trade_data` ( +  `itemGuid` int(11) unsigned NOT NULL COMMENT 'Item GUID', +  `allowedPlayers` text NOT NULL COMMENT 'Space separated GUID list of players who can receive this item in trade', +  PRIMARY KEY (`itemGuid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='Item Refund System'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `item_soulbound_trade_data` +-- + +LOCK TABLES `item_soulbound_trade_data` WRITE; +/*!40000 ALTER TABLE `item_soulbound_trade_data` DISABLE KEYS */; +/*!40000 ALTER TABLE `item_soulbound_trade_data` ENABLE KEYS */; +UNLOCK TABLES; + +--  -- Table structure for table `lag_reports`  -- diff --git a/sql/updates/10030_characters_item_soulbound_trade_data.sql b/sql/updates/10030_characters_item_soulbound_trade_data.sql new file mode 100644 index 00000000000..e6f43debb9b --- /dev/null +++ b/sql/updates/10030_characters_item_soulbound_trade_data.sql @@ -0,0 +1,6 @@ +DROP TABLE IF EXISTS `item_soulbound_trade_data`; +CREATE TABLE `item_soulbound_trade_data` ( +  `itemGuid` int(11) unsigned NOT NULL COMMENT 'Item GUID', +  `allowedPlayers` text NOT NULL COMMENT 'Space separated GUID list of players who can receive this item in trade', +  PRIMARY KEY (`itemGuid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='Item Refund System'; 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,  };  | 
