Object.h
Item.h
Bag.h
这里的背包继承了物品类,而物品类又继承了对象Object类
#define MAX_BAG_SIZE 36
// Bag Storage space
Item* m_bagslot[MAX_BAG_SIZE];
可以看到这里是存了指针数组,上限是36(因为c with class的关系,而且对于游戏来说内存很重要)
// DB operations
// overwrite virtual Item::SaveToDB
void SaveToDB(CharacterDatabaseTransaction trans) override;
// overwrite virtual Item::LoadFromDB
bool LoadFromDB(ObjectGuid::LowType guid, ObjectGuid owner_guid, Field* fields, uint32 entry) override;
// overwrite virtual Item::DeleteFromDB
void DeleteFromDB(CharacterDatabaseTransaction trans) override;
DROP TABLE IF EXISTS `character_inventory`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `character_inventory` (
`guid` int unsigned NOT NULL DEFAULT '0' COMMENT 'Global Unique Identifier',
`bag` int unsigned NOT NULL DEFAULT '0',
`slot` tinyint unsigned NOT NULL DEFAULT '0',
`item` int unsigned NOT NULL DEFAULT '0' COMMENT 'Item Global Unique Identifier',
PRIMARY KEY (`item`),
UNIQUE KEY `guid` (`guid`,`bag`,`slot`),
KEY `idx_guid` (`guid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Player System';
/*!40101 SET character_set_client = @saved_cs_client */;
①guid 人物id
②slot 槽位id
③item 道具id
④bag 背包索引(魔兽可携带多个背包)
表格使用的存储引擎为 InnoDB,字符集为 utf8mb4,排序规则为 utf8mb4_unicode_ci。整个表格用于存储玩家系统中与角色背包相关的数据。
①PRIMARY KEY (item):将 item 列定义为主键,确保每个物品在表格中是唯一的。
②UNIQUE KEY guid (guid,bag,slot):创建一个唯一键,由 guid、bag 和 slot 三列组成,确保每个背包槽位中的物品在表格中是唯一的。
③KEY idx_guid (guid):创建一个索引,以 guid 列作为索引,用于快速检索与特定角色相关的背包信息。
①改变数据表的存储格式
ALTER TABLE `character_inventory` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
解释:
这个 ALTER TABLE 语句的目的是将 character_inventory 表的字符集从现有的字符集转换为 utf8mb4。 utf8mb4 字符集支持存储更广泛的字符,包括一些特殊的 Unicode 字符,如表情符号。校对规则 utf8mb4_unicode_ci 则用于指定字符的排序和比较规则。
②删除对应列
ALTER TABLE `character_inventory` DROP COLUMN `item_template`;
解释:
这个 ALTER TABLE 语句的目的是从 character_inventory 表中删除名为 item_template 的列。执行该语句后,表中的数据将不再包含 item_template 列的值,并且该列将从表结构中删除。
CREATE TABLE `item_instance` (
`guid` int unsigned NOT NULL DEFAULT '0',
`itemEntry` mediumint unsigned NOT NULL DEFAULT '0',
`owner_guid` int unsigned NOT NULL DEFAULT '0',
`creatorGuid` int unsigned NOT NULL DEFAULT '0',
`giftCreatorGuid` int unsigned NOT NULL DEFAULT '0',
`count` int unsigned NOT NULL DEFAULT '1',
`duration` int NOT NULL DEFAULT '0',
`charges` tinytext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
`flags` mediumint unsigned NOT NULL DEFAULT '0',
`enchantments` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`randomPropertyId` smallint NOT NULL DEFAULT '0',
`durability` smallint unsigned NOT NULL DEFAULT '0',
`playedTime` int unsigned NOT NULL DEFAULT '0',
`text` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
PRIMARY KEY (`guid`),
KEY `idx_owner_guid` (`owner_guid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Item System';
/*!40101 SET character_set_client = @saved_cs_client */;
目的:这个SQL语句的目的是创建一个名为 item_instance 的表,用于存储物品实例的相关信息。
guid:物品唯一id
owner_guid:物品归属obj的id
①删除背包物品(全服唯一的物品id)CHAR_DEL_CHAR_INVENTORY_BY_ITEM
PrepareStatement(CHAR_DEL_CHAR_INVENTORY_BY_ITEM,
"DELETE FROM character_inventory WHERE item = ?", CONNECTION_ASYNC);
②删除数据内对应背包id、对应格子、对应人物的数据信息
PrepareStatement(CHAR_DEL_CHAR_INVENTORY_BY_BAG_SLOT,
"DELETE FROM character_inventory WHERE bag = ? AND slot = ? AND guid = ?", CONNECTION_ASYNC);
因为之前可以看到玩家背包表有做唯一索引
UNIQUE KEY `guid` (`guid`,`bag`,`slot`)
③查询背包里面单个物品id对应的持续时间、槽位、背包id、槽位id、标志位flags等信息
PrepareStatement(CHAR_SEL_CHARACTER_INVENTORY,
"SELECT creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, playedTime, text, bag, slot,item, itemEntry
FROM character_inventory ci #给character_inventory起别名ci
JOIN item_instance ii #联表查询item_instance,给item_instance起别名ii
ON ci.item = ii.guid WHERE ci.guid = ? #查询单个物品在
ORDER BY bag, slot", CONNECTION_ASYNC);
④给character_inventory 执行插入操作
PrepareStatement(CHAR_REP_INVENTORY_ITEM,
"REPLACE INTO character_inventory (guid, bag, slot, item) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC);
Bag::Bag(): Item()
{
m_objectType |= TYPEMASK_CONTAINER; //标志是物品是背包类型
m_objectTypeId = TYPEID_CONTAINER; //标志这个obj是背包
m_valuesCount = CONTAINER_END;
memset(m_bagslot, 0, sizeof(Item*) * MAX_BAG_SIZE);
}
Bag::~Bag()
{
for (uint8 i = 0; i < MAX_BAG_SIZE; ++i)
if (Item* item = m_bagslot[i])
{
if (item->IsInWorld()) //背包在地图上,那就需要从地图实例里面去除
{
TC_LOG_FATAL("entities.player.items", "Item {} (slot {}, bag slot {}) in bag {} (slot {}, bag slot {}, m_bagslot {}) is to be deleted but is still in world.",
item->GetEntry(), (uint32)item->GetSlot(), (uint32)item->GetBagSlot(),
GetEntry(), (uint32)GetSlot(), (uint32)GetBagSlot(), (uint32)i);
item->RemoveFromWorld();
}
delete m_bagslot[i];
}
}
//把实例添加到世界
void Bag::AddToWorld()
{
Item::AddToWorld();
for (uint32 i = 0; i < GetBagSize(); ++i)
if (m_bagslot[i])
m_bagslot[i]->AddToWorld();
}
//从世界里面去除实例
void Bag::RemoveFromWorld()
{
for (uint32 i = 0; i < GetBagSize(); ++i)
if (m_bagslot[i])
m_bagslot[i]->RemoveFromWorld();
Item::RemoveFromWorld();
}
bool Bag::Create(ObjectGuid::LowType guidlow, uint32 itemid, Player const* owner)
{
//读取物品的配置信息,例如堆叠数量、过期时间、耐久度
ItemTemplate const* itemProto = sObjectMgr->GetItemTemplate(itemid);
if (!itemProto || itemProto->ContainerSlots > MAX_BAG_SIZE)
return false;
Object::_Create(guidlow, 0, HighGuid::Container);
//标志位字段设置物品id
SetEntry(itemid);
//OBJECT_FIELD_SCALE_X 是用来控制游戏中物体的缩放比例的字段
SetObjectScale(1.0f);
if (owner)
{
//如果这个背包添加道具是添加到玩家身上,在对应标志位填玩家id
SetGuidValue(ITEM_FIELD_OWNER, owner->GetGUID());
SetGuidValue(ITEM_FIELD_CONTAINED, owner->GetGUID());
}
//ITEM_FIELD_DURABILITY:物品的当前耐久度。
//ITEM_FIELD_MAXDURABILITY:物品的最大耐久度
SetUInt32Value(ITEM_FIELD_MAXDURABILITY, itemProto->MaxDurability);
SetUInt32Value(ITEM_FIELD_DURABILITY, itemProto->MaxDurability);
//ITEM_FIELD_STACK_COUNT:物品的堆叠数量
SetUInt32Value(ITEM_FIELD_STACK_COUNT, 1);
// Setting the number of Slots the Container has
//根据配置设置背包槽位数
SetUInt32Value(CONTAINER_FIELD_NUM_SLOTS, itemProto->ContainerSlots);
// Cleaning 20 slots
for (uint8 i = 0; i < MAX_BAG_SIZE; ++i)
{
//将背包对象的每个槽位的GUID值设置为空,以确保背包对象创建时的初始状态是空的,没有任何物品。
SetGuidValue(CONTAINER_FIELD_SLOT_1 + (i*2), ObjectGuid::Empty);
m_bagslot[i] = nullptr;
}
return true;
}
uint32 Bag::GetItemCount(uint32 item, Item* eItem) const
{
Item* pItem;
uint32 count = 0;
for (uint32 i=0; i < GetBagSize(); ++i)
{
pItem = m_bagslot[i];
if (pItem && pItem != eItem && pItem->GetEntry() == item)
count += pItem->GetCount();
}
if (eItem && eItem->GetTemplate()->GemProperties)
{
for (uint32 i=0; i < GetBagSize(); ++i)
{
pItem = m_bagslot[i];
if (pItem && pItem != eItem && pItem->GetTemplate()->Socket[0].Color)
count += pItem->GetGemCountWithID(item);
}
}
return count;
}
备注
uint32 limitCategory表示物品的标签
代码
uint32 Bag::GetItemCountWithLimitCategory(uint32 limitCategory, Item* skipItem) const
{
uint32 count = 0;
for (uint32 i = 0; i < GetBagSize(); ++i)
if (Item* pItem = m_bagslot[i])
if (pItem != skipItem)
if (ItemTemplate const* pProto = pItem->GetTemplate())
if (pProto->ItemLimitCategory == limitCategory)
count += m_bagslot[i]->GetCount();
return count;
}
uint8 Bag::GetSlotByItemGUID(ObjectGuid guid) const
{
for (uint32 i = 0; i < GetBagSize(); ++i)
if (m_bagslot[i] != 0)
if (m_bagslot[i]->GetGUID() == guid)
return i;
return NULL_SLOT;
}
①玩家身上有150个物品格子,而在这个格子里面,有的物品是背包,那么这个背包就可以存储最多36个物品(这也是为什么Bag继承Item的原因,因为可以根据Item找到对应的Bag)
②从下面的枚举可以看出,玩家身上固定第19到23是背包的槽位,1到18是身上装备槽位
enum PlayerSlots
{
// first slot for item stored (in any way in player m_items data)
PLAYER_SLOT_START = 0,
// last+1 slot for item stored (in any way in player m_items data)
PLAYER_SLOT_END = 150,
PLAYER_SLOTS_COUNT = (PLAYER_SLOT_END - PLAYER_SLOT_START)
};
class player{
.....
Item* m_items[PLAYER_SLOTS_COUNT];
....
}
---------------------
背包枚举:
enum EquipmentSlots : uint8 // 19 slots
{
EQUIPMENT_SLOT_START = 0,
EQUIPMENT_SLOT_HEAD = 0,
EQUIPMENT_SLOT_NECK = 1,
EQUIPMENT_SLOT_SHOULDERS = 2,
EQUIPMENT_SLOT_BODY = 3,
EQUIPMENT_SLOT_CHEST = 4,
EQUIPMENT_SLOT_WAIST = 5,
EQUIPMENT_SLOT_LEGS = 6,
EQUIPMENT_SLOT_FEET = 7,
EQUIPMENT_SLOT_WRISTS = 8,
EQUIPMENT_SLOT_HANDS = 9,
EQUIPMENT_SLOT_FINGER1 = 10,
EQUIPMENT_SLOT_FINGER2 = 11,
EQUIPMENT_SLOT_TRINKET1 = 12,
EQUIPMENT_SLOT_TRINKET2 = 13,
EQUIPMENT_SLOT_BACK = 14,
EQUIPMENT_SLOT_MAINHAND = 15,
EQUIPMENT_SLOT_OFFHAND = 16,
EQUIPMENT_SLOT_RANGED = 17,
EQUIPMENT_SLOT_TABARD = 18,
EQUIPMENT_SLOT_END = 19
};
enum InventorySlots : uint8 // 4 slots
{
INVENTORY_SLOT_BAG_START = 19,
INVENTORY_SLOT_BAG_END = 23
};
enum InventoryPackSlots : uint8 // 16 slots
{
INVENTORY_SLOT_ITEM_START = 23,
INVENTORY_SLOT_ITEM_END = 39
};
enum BankItemSlots // 28 slots
{
BANK_SLOT_ITEM_START = 39,
BANK_SLOT_ITEM_END = 67
};
enum BankBagSlots // 7 slots
{
BANK_SLOT_BAG_START = 67,
BANK_SLOT_BAG_END = 74
};
enum BuyBackSlots // 12 slots
{
// stored in m_buybackitems
BUYBACK_SLOT_START = 74,
BUYBACK_SLOT_END = 86
};
enum KeyRingSlots : uint8 // 32 slots
{
KEYRING_SLOT_START = 86,
KEYRING_SLOT_END = 118
};
enum CurrencyTokenSlots // 32 slots
{
CURRENCYTOKEN_SLOT_START = 118,
CURRENCYTOKEN_SLOT_END = 150
};
Item* m_items[PLAYER_SLOTS_COUNT];
void Player::BuildCreateUpdateBlockForPlayer(UpdateData* data, Player* target) const
{
if (target == this)
{
//遍历身上装备的装备枚举,到EQUIPMENT_SLOT_END
for (uint8 i = 0; i < EQUIPMENT_SLOT_END; ++i)
{
if (m_items[i] == nullptr)
continue;
//通知客户端创建对应实体
m_items[i]->BuildCreateUpdateBlockForPlayer(data, target);
}
for (uint8 i = INVENTORY_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i)
{
if (m_items[i] == nullptr)
continue;
m_items[i]->BuildCreateUpdateBlockForPlayer(data, target);
}
for (uint8 i = KEYRING_SLOT_START; i < CURRENCYTOKEN_SLOT_END; ++i)
{
if (m_items[i] == nullptr)
continue;
m_items[i]->BuildCreateUpdateBlockForPlayer(data, target);
}
}
Unit::BuildCreateUpdateBlockForPlayer(data, target);
}
enum EquipmentSlots : uint8 // 19 slots
{
EQUIPMENT_SLOT_START = 0,
EQUIPMENT_SLOT_HEAD = 0,
EQUIPMENT_SLOT_NECK = 1,
EQUIPMENT_SLOT_SHOULDERS = 2,
EQUIPMENT_SLOT_BODY = 3,
EQUIPMENT_SLOT_CHEST = 4,
EQUIPMENT_SLOT_WAIST = 5,
EQUIPMENT_SLOT_LEGS = 6,
EQUIPMENT_SLOT_FEET = 7,
EQUIPMENT_SLOT_WRISTS = 8,
EQUIPMENT_SLOT_HANDS = 9,
EQUIPMENT_SLOT_FINGER1 = 10,
EQUIPMENT_SLOT_FINGER2 = 11,
EQUIPMENT_SLOT_TRINKET1 = 12,
EQUIPMENT_SLOT_TRINKET2 = 13,
EQUIPMENT_SLOT_BACK = 14,
EQUIPMENT_SLOT_MAINHAND = 15,
EQUIPMENT_SLOT_OFFHAND = 16,
EQUIPMENT_SLOT_RANGED = 17,
EQUIPMENT_SLOT_TABARD = 18,
EQUIPMENT_SLOT_END = 19
};
--------------------------------------
EQUIPMENT_SLOT_HEAD:头部
EQUIPMENT_SLOT_NECK:颈部
EQUIPMENT_SLOT_SHOULDERS:肩部
EQUIPMENT_SLOT_BODY:身体
EQUIPMENT_SLOT_CHEST:胸部
EQUIPMENT_SLOT_WAIST:腰部
EQUIPMENT_SLOT_LEGS:腿部
EQUIPMENT_SLOT_FEET:脚部
EQUIPMENT_SLOT_WRISTS:手腕
EQUIPMENT_SLOT_HANDS:手部
EQUIPMENT_SLOT_FINGER1:手指1
EQUIPMENT_SLOT_FINGER2:手指2
EQUIPMENT_SLOT_TRINKET1:饰品1
EQUIPMENT_SLOT_TRINKET2:饰品2
EQUIPMENT_SLOT_BACK:背部
EQUIPMENT_SLOT_MAINHAND:主手
EQUIPMENT_SLOT_OFFHAND:副手
EQUIPMENT_SLOT_RANGED:远程
EQUIPMENT_SLOT_TABARD:战袍
#include
#include
// 定义位数分配
#define TIMESTAMP_BITS 32
#define SERVER_TYPE_BITS 5
#define SERVER_ID_BITS 5
#define OBJ_TYPE_BITS 8
#define SEQ_ID_BITS 14
// 定义位偏移
#define SERVER_TYPE_SHIFT (TIMESTAMP_BITS)
#define SERVER_ID_SHIFT (SERVER_TYPE_SHIFT + SERVER_TYPE_BITS)
#define OBJ_TYPE_SHIFT (SERVER_ID_SHIFT + SERVER_ID_BITS)
#define SEQ_ID_SHIFT (OBJ_TYPE_SHIFT + OBJ_TYPE_BITS)
// 定义掩码
#define TIMESTAMP_MASK ((1ULL << TIMESTAMP_BITS) - 1)
#define SERVER_TYPE_MASK ((1ULL << SERVER_TYPE_BITS) - 1)
#define SERVER_ID_MASK ((1ULL << SERVER_ID_BITS) - 1)
#define OBJ_TYPE_MASK ((1ULL << OBJ_TYPE_BITS) - 1)
#define SEQ_ID_MASK ((1ULL << SEQ_ID_BITS) - 1)
// 宏定义生成ID
#define GENERATE_ID(serverType, serverID, objType, seqID) \
(((time(nullptr) & TIMESTAMP_MASK)) | \
(((serverType) & SERVER_TYPE_MASK) << SERVER_TYPE_SHIFT) | \
(((serverID) & SERVER_ID_MASK) << SERVER_ID_SHIFT) | \
(((objType) & OBJ_TYPE_MASK) << OBJ_TYPE_SHIFT) | \
(((seqID) & SEQ_ID_MASK) << SEQ_ID_SHIFT))
int main() {
// 模拟服务器类型、服务器ID、Obj类型和秒级产生的ID
uint64_t serverType = 7; // 5位示例值
uint64_t serverID = 5; // 5位示例值
uint64_t objType = 42; // 8位示例值
uint64_t seqID = 1234; // 14位示例值
// 生成ID并输出
uint64_t generatedID = GENERATE_ID(serverType, serverID, objType, seqID);
std::cout << "Generated ID: " << generatedID << std::endl;
return 0;
}
①根据物品id去物品表找到原型,然后根据这个配置读取持续时间、堆叠次数等信息
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemEntry);
②如果获得物品大于堆叠次数,那么物品数量就是最大堆叠数量
if (count > proto->GetMaxStackSize())
count = proto->GetMaxStackSize();
③构造这个item对象,根据类型是不是背包,若是背包则构建背包(从前面代码可以看到包裹bag继承了item这个类)
Item* item = NewItemOrBag(proto);
-----------------
inline Item* NewItemOrBag(ItemTemplate const* proto)
{
return (proto->InventoryType == INVTYPE_BAG) ? new Bag : new Item;
}
④根据单例传入的自增id和物品原型id自增填充Item这个对象的内容
而实际调用的是Item::Create函数:
bool Item::Create(ObjectGuid::LowType guidlow
, uint32 itemId, Player const* owner)
<1>这里的guildlow返回的是item这个道具id的Mananger对应的自增id
<2>接着调用内部的_create函数
guidhigh: 0x4000
entry: 0
guildlow: 32位的自增id
调用了ObjectGuid的构造函数
也就是说:
item_Id是由如下的不同组合而成
1)高48位的obj类型
2)24位到47位表示entry
3)低位32位表示对应mgr的自增id
if (item->Create(sObjectMgr->GetGenerator<HighGuid::Item>().Generate(), itemEntry, player))
{
item->SetCount(count);
return item;
}
else
delete item;
------------------------
bool Item::Create(ObjectGuid::LowType guidlow, uint32 itemId, Player const* owner)
{
Object::_Create(guidlow, 0, HighGuid::Item);
SetEntry(itemId);
SetObjectScale(1.0f);
if (owner)
{
SetGuidValue(ITEM_FIELD_OWNER, owner->GetGUID());
SetGuidValue(ITEM_FIELD_CONTAINED, owner->GetGUID());
}
ItemTemplate const* itemProto = sObjectMgr->GetItemTemplate(itemId);
if (!itemProto)
return false;
SetUInt32Value(ITEM_FIELD_STACK_COUNT, 1);
SetUInt32Value(ITEM_FIELD_MAXDURABILITY, itemProto->MaxDurability);
SetUInt32Value(ITEM_FIELD_DURABILITY, itemProto->MaxDurability);
for (uint8 i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i)
SetSpellCharges(i, itemProto->Spells[i].SpellCharges);
SetUInt32Value(ITEM_FIELD_DURATION, itemProto->Duration);
SetUInt32Value(ITEM_FIELD_CREATE_PLAYED_TIME, 0);
return true;
}
Item* Item::CreateItem(uint32 itemEntry, uint32 count, Player const* player /*= nullptr*/)
{
if (count < 1)
return nullptr; //don't create item at zero count
ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemEntry);
if (proto)
{
if (count > proto->GetMaxStackSize())
count = proto->GetMaxStackSize();
ASSERT_NODEBUGINFO(count != 0 && "pProto->Stackable == 0 but checked at loading already");
Item* item = NewItemOrBag(proto);
if (item->Create(sObjectMgr->GetGenerator<HighGuid::Item>().Generate(), itemEntry, player))
{
item->SetCount(count);
return item;
}
else
delete item;
}
else
ABORT();
return nullptr;
}
class TC_GAME_API Bag : public Item
{
public:
Bag();
~Bag();
void AddToWorld() override;
void RemoveFromWorld() override;
bool Create(ObjectGuid::LowType guidlow, uint32 itemid, Player const* owner) override;
void StoreItem(uint8 slot, Item* pItem, bool update);
void RemoveItem(uint8 slot, bool update);
Item* GetItemByPos(uint8 slot) const;
uint32 GetItemCount(uint32 item, Item* eItem = nullptr) const;
uint32 GetItemCountWithLimitCategory(uint32 limitCategory, Item* skipItem = nullptr) const;
uint8 GetSlotByItemGUID(ObjectGuid guid) const;
bool IsEmpty() const;
uint32 GetFreeSlots() const;
uint32 GetBagSize() const { return GetUInt32Value(CONTAINER_FIELD_NUM_SLOTS); }
// DB operations
// overwrite virtual Item::SaveToDB
void SaveToDB(CharacterDatabaseTransaction trans) override;
// overwrite virtual Item::LoadFromDB
bool LoadFromDB(ObjectGuid::LowType guid, ObjectGuid owner_guid, Field* fields, uint32 entry) override;
// overwrite virtual Item::DeleteFromDB
void DeleteFromDB(CharacterDatabaseTransaction trans) override;
void BuildCreateUpdateBlockForPlayer(UpdateData* data, Player* target) const override;
std::string GetDebugInfo() const override;
protected:
// Bag Storage space
Item* m_bagslot[MAX_BAG_SIZE];
};