魔兽3.3.5版本背包模块代码拆分解析(从之前的文章中拆分出来)
文章目录
不想写在 魔兽服务器学习-笔记之中了,之前的文章篇幅太长,代码在最后的模块
一、涉及到的文件
Object.h
Item.h
Bag.h
这里的背包继承了物品类,而物品类又继承了对象Object类
- 注意点
因为这里避免看的越多,所以这里从Bag.h看起,因为从Object.h看起来的话,根本看不完
二、先看成员变量(单个背包的存储数据)
#define MAX_BAG_SIZE 36
// Bag Storage space
Item* m_bagslot[MAX_BAG_SIZE];
可以看到这里是存了指针数组,上限是36(因为c with class的关系,而且对于游戏来说内存很重要)
三、再看玩家背包和DB的交互
- 背包类和DB交互的代码
// 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;
- 玩家背包数据的db存储sql语句
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(表中唯一的id,可能是做全服唯一的道具用的)
④bag 背包索引(魔兽可携带多个背包)
表格使用的存储引擎为 InnoDB,字符集为 utf8mb4,排序规则为 utf8mb4_unicode_ci。整个表格用于存储玩家系统中与角色背包相关的数据。
1)逐行解析db表
①PRIMARY KEY (item):将 item 列定义为主键,确保每个物品在表格中是唯一的。
②UNIQUE KEY guid (guid,bag,slot):创建一个唯一键,由 guid、bag 和 slot 三列组成,确保每个背包槽位中的物品在表格中是唯一的。
③KEY idx_guid (guid):创建一个索引,以 guid 列作为索引,用于快速检索与特定角色相关的背包信息。
2)sql语句变更记录(记录一些sql语句对数据存储的影响)
①改变数据表的存储格式
ALTER TABLE `character_inventory` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
- ALTER TABLE:用于修改数据库表的命令。
- character_inventory:表名,指定要修改的表为 character_inventory。
- CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci:用于指定要将表的字符集转换为 utf8mb4,并使用 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:表名,指定要修改的表为 character_inventory。
- DROP COLUMN:指定要删除列的操作。
- item_template:要删除的列名,即 character_inventory 表中的 item_template 列。
解释:
这个 ALTER TABLE 语句的目的是从 character_inventory 表中删除名为 item_template 的列。执行该语句后,表中的数据将不再包含 item_template 列的值,并且该列将从表结构中删除。
3)额外提一嘴物品在mysql的存储
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
4)预编译背包的物品和db交互的sql语句
①删除背包物品(全服唯一的物品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);
四、解析成员函数
1)构造函数
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);
}
2)析构函数
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];
}
}
3)实体添加到世界
//把实例添加到世界
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();
}
4)背包添加物品
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;
}
5)获得对应id的物品数量
- 备注
eItem->GetTemplate()->GemProperties用于计算背包宝石的数量 - 代码
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;
}
6)根据物品标签获得对应物品数量
-
备注
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;
}
7)根据物品id获取对应背包内物品的槽位
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)
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];
....
}
七、已上阵的装备代码
1)建立人物碰撞体代码讲解开始
- 代码讲解
遍历身上装备道具,通知客户端创建对应地图实体 - 对应数据
也是存在之前说的道具数组里面
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);
}
2)源码枚举
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:战袍
六、代码
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];
};