http://hi.baidu.com/zyy503950958/home
下载和安装msysgit,用于代码管理。我使用的是Git-1.6.5.1-preview20091022.exe。
- 下载和安装tortoisegit,用于代码管理。我使用的是TortoiseGit-1.3.2.0-32bit.msi 。
- 使用git://github.com/mangos/mangos.git,从github提取mangos代码。
- 采用Git GUI工具的Clone Existing Repository,得到 mangos代码。(我的是9560,安装UDB参考这)
- 使用git://github.com/scriptdev2/scriptdev2.git,从github提取scriptdev2代码。
- 采用Git GUI工具的Clone Existing Repository,得到 scriptdev2代码.。
- 编译mangos。我用的是VC9,打开mangos\win\mangosdVC90.sln进行构造,构造完成后,会得到mangos\bin\Win32_Debug文件夹。
- 编译scriptdev2。我用的是VC9,打开mangos\win\scriptdev2VC90.sln进行构造,构造完成后,会得到scriptdev2\bin\Win32_Debug文件夹。也可参考
pandore
- 拷贝mangos\src\mangosd目录下的mangosd.conf.dist.in为mangos\bin\Win32_Debug\mangosd.conf。
- 拷贝mangos\src\realmd目录下的realmd.conf.dist.in为mangos\bin\Win32_Debug\realmd.conf。
- 从http://www.wowtaiwan.com.tw/下载和安装台服WOW,并升级到最新版本。我使用的是台服WOW 3.3.2 build 11403。
- Maps, VMAPs and DBCs,采用MaNGOS的工具ad从wow的MPQ中抽取map,得到的所有的map数据文件,文件命名规范为map_id(3位) tileY(2位) tileX(2位).map,如文件名为0002035.map,代表的是Azeroth(地图id为000,tile坐标为(35,20). 注:WOW客户端的Tile对应mangos中的grid,WOW客户端的Chunk对应mangos中的cell(1cell = 4 chunk)。
- 按照常规流程(包括建立数据库和配置服务器)把服务器跑起来,使用account create zzh1234567 zzh1234567 创建一个账号,使用account set gmlevel zzh1234567 3设置为超级用户。
- 配置好客户端后,运行WOW,顺利登陆,呵呵
- 通过安装UDB来丰富场景。FULL DB 9560 : HERE,参考这,在我安装DB9560的时候,发现Mangos在LoadCreatureAddons的时候,加载creature template addons出错,Mangos只要求creature template addons有7个字段,而creature template addons有9个字段。我现在只是简单地跳过LoadCreatureAddons的调用。
14. 如果要用PerfHUB来调试,可以二进制打开wow.exe,把其中的53 52 6A 01 6A 00 50 8B 41 40改成53 52 6A 02 6A 01 50 8B 41 40后另存。具体可以参考用PerfHUD来调试商业游戏。改完后,托exe到PerfHUB的图标上就ok了。
运行Mangos (运行Mangos一节过期,是以前我针对国服3.1.3版本进行的安装配置)
|
Mangos代码阅读 Mangos有13个工程。 使用了4个外部工具库,分别是:
Mangos的实现分为:登录服务器(realmd)和世界服务器(mangosd+game)。realmd和mangos共用了Mangos公共库(shared)。 工程shared 提供了通用功能,包括了数据库的封装类,实现了对MySql的访问,同样,我们可以编写派生类来支持其他的数据库。 工程script 提供了脚本接口,并实现了简单的几个脚本,封装为DLL,提供给game使用,具体可参考:MaNGOS脚本接口。 通过使用不同的脚本DLL来替换Mangos中的AI实现,可以让game具有更强的AI。ScriptDev2 就是一个这样的库。ScriptDev2 is a replacement for the Script Library that comes with MaNGOS( http://www.getmangos.com ) written in C++ and is compatible with Windows and Linux. It provides scripts for NPCs, Boss events, and Items currently. Once ScriptDev2 is compiled it is automatically run by MaNGOS on server startup. 工程mangosd mangos是世界服务器的管理器,负责初始化工作和启动世界服务器各层的线程,这些工作主要是由类Master来实现。具体是:
mangosd的线程总共有(1+3+1+1+1+2 +1 =10)10个线程。
工程g3dlite:游戏逻辑层的底层库 工程framework:系统框架 工程realm 负责登陆和选择游戏服务器,进行负载均衡。用到了C++ Sockets Library进行登录处理,采用select I/O模型。实现了Wow, Mangos登录时的SRP6认证。客户端作为它的client连接到realm server认证和选择了mangos server就断开。 而mangos server和realm server则不进行连接,只是通过数据库交互数据:mangos server把自己的状态和拥有的角色数放入库中。realm server会读取数据库中的这些信息来获知mangos server的状态。
登录处理: user登录到realm server进行身份认证,并选择登录上哪个mangos server。user登录到mangos server后,将不再和realm server交互。 参考: Wow 服务器解析 |
工程game
game:是Mangos的核心代码,网络层和逻辑层代码(采用了ACE反应器(Reactor)模式)
网络层:
- WorldSocket :负责网络IO,而类WorldSession负责逻辑处理。WorldSocket和WorldSession分别在独立的线程ReactorRunnable和WorldRunnable中运行,使用WorldSession中的消息队列_recvQueue来进行数据缓冲。在WorldSocket接收到网咯输入后,会调用m_Session->QueuePacket (new_pct);把网络包放入WorldSession的_recvQueue。所以,可以看到WorldSocket 是Mangos game的网络层,而WorldSession是逻辑处理层。WorldSocketMgr是网络层的一个管理器,它负责指派WorldSocket归哪个ReactorRunnable管理(Mangos可创建多个ReactorRunnable,缺省是2个)。
- WorldSocketMgr管理所有的连接WorldSocket。WorldSocketMgr的WorldSocketMgr::StartNetwork对8085(缺省)端口进行侦听。
逻辑层:
- 类World实现了wow的World,所有的逻辑处理。逻辑处理的循环是在World::Update中。循环处理包括:
- 刷新更新定时器
- 刷新游戏定时器和处理游戏关闭
- 处理日常任务
- 处理拍卖
- 刷新Sessions。World::UpdateSessions会调用所有WorldSession的WorldSession::Update。在WorldSession::Update中进行逻辑处理。
- 处理天气
- 刷新uptime table
- 刷新Objects,包括maps,transport,creatures,,,,
- 刷新所有running battlegrounds
- 刷新SqlResultQueue, 逻辑层和数据层是通过Queue来进行异步操作的。(用了AsyncPQuery和SqlResultQueue)
- 处理尸体移除
- 处理游戏事件
- 处理 Move all creatures with "delayed move" and remove and delete all objects with "delayed remove"
- 处理InstanceSaveManager的刷新
- 调用World::ProcessCliCommands,处理CLI。从cliCmdQueue取得cmd进行解析执行。所有有效的Cmd,都可以在ChatHandler::getCommandTable中找到。
- 类WorldSession: 类WorldSession负责逻辑处理。
- void WorldSession::SendPacket(WorldPacket const* packet) 负责发包给客户端,直接发包,没有输出缓冲队列。
- 在WorldSession::Update中进行逻辑处理。World::UpdateSessions会调用所有WorldSession的WorldSession::Update。
- 执行语句OpcodeHandler& opHandle = opcodeTable[packet->GetOpcode()];得到opHandle
- 根据得到的opHandle,执行(this->*opHandle.handler)(*packet);
- WorldSession::HandlePlayerLogin处理玩家登陆游戏。
- 构建Player
- Player::LoadFromDB从数据库中加载玩家数据。在Player::LoadFromDB中会调用SetMap(MapManager::Instance().CreateMap(GetMapId(), this));加载当前player所在的map
- Player::SetPosition在Player运动的时候,改变位置,保存处理夸区。
- 类MapManager
- 类Map实现了一个state machine,采用state pattern组织了Gid的4个state object:InvalidState;ActiveState;IdleState;RemovalState。
game中的定时器有:
/// Timers for different object refresh rates
enum WorldTimers
{
WUPDATE_OBJECTS = 0, // 间隔为0
WUPDATE_SESSIONS = 1, // 间隔为0
WUPDATE_AUCTIONS = 2,// 间隔为60秒
WUPDATE_WEATHERS = 3,// 间隔为1秒
WUPDATE_UPTIME = 4,
WUPDATE_CORPSES = 5,
WUPDATE_EVENTS = 6,
WUPDATE_COUNT = 7
};
game中的管理器有:
- ObjectMgr : MaNGOS 中的Object Management
- ObjectMgr::LoadMangosStrings: 把mMangosStringLocaleMap 关联到mangos_string table
- ObjectMgr::LoadScriptNames:m_scriptNames 关联到tables: creature_template;gameobject_template;item_template;areatrigger_scripts;instance_template
- mCreatureLocaleMap 关联到locales_creature table
- mGameObjectLocaleMap关联到locales_gameobject table
- mItemLocaleMap关联到locales_item table
- mQuestLocaleMap –> locales_quest
- mNpcTextLocaleMap –> locales_npc_text
- mPageTextLocaleMap –> locales_page_text
- mGossipMenuItemsLocaleMap –> locales_gossip_menu_option
- mPointOfInterestLocaleMap –> locales_points_of_interest
- …
- GameEventMgr
- BattleGroundMgr
- AccountMgr
- InstanceSaveManager
- SpellMgr
- GMTicketMgr
- PoolManager
- WaypointManager
- CreatureEventAIMgr
- AuctionHouseMgr
- LootValidatorRefManager
对象类层次
对象的类层次如下,所有的Object都由ObjectMgr进行管理。ObjectMgr以GUID方式,管理了characters,creature,item_instance,gameobject,auctionhouse,mail,item_text,corpse,arena_team,character_equipmentsets。
// indexes of BattlemasterList.dbc
enum BattleGroundTypeId
{
BATTLEGROUND_TYPE_NONE = 0,
BATTLEGROUND_AV = 1,
BATTLEGROUND_WS = 2,
BATTLEGROUND_AB = 3,
BATTLEGROUND_NA = 4,
BATTLEGROUND_BE = 5,
BATTLEGROUND_AA = 6,
BATTLEGROUND_EY = 7,
BATTLEGROUND_RL = 8,
BATTLEGROUND_SA = 9,
BATTLEGROUND_DS = 10,
BATTLEGROUND_RV = 11,
BATTLEGROUND_IC = 30,
BATTLEGROUND_ABG = 32
};
战场代码名字解释
AB - Arathi Basin ALX
AV - Alterac Valley AS
Ey - Eye of the Storm FB
BE - Blade Edge arena 刀锋山竞技场
AA - dunno :]
RL - Ruins of Lordareon arena 洛丹伦废墟竞技场
NA - Nagrand Arena 纳格兰竞技场
Player状态
/// Player state
enum SessionStatus
{
STATUS_AUTHED = 0, ///< Player authenticated (_player==NULL, m_playerRecentlyLogout = false or will be reset before handler call, m_GUID have garbage)
STATUS_LOGGEDIN, ///< Player in game (_player!=NULL, m_GUID == _player->GetGUID(), inWorld())
STATUS_TRANSFER, ///< Player transferring to another map (_player!=NULL, m_GUID == _player->GetGUID(), !inWorld())
STATUS_LOGGEDIN_OR_RECENTLY_LOGGOUT, ///< _player!= NULL or _player==NULL && m_playerRecentlyLogout, m_GUID store last _player guid)
STATUS_NEVER ///< Opcode not accepted from client (deprecated or server side only)
};
生物状态
enum DeathState
{
ALIVE = 0,
JUST_DIED = 1,
CORPSE = 2,
DEAD = 3,
JUST_ALIVED = 4,
DEAD_FALLING= 5
};
玩家登陆
服务器端在连接打开后,会发SMSG_AUTH_CHALLENGE到客户端。客户端从服务器端发送回来的种子和 SRP6 数据中产生随机种子,生成 SHA1 字符串,用这些数据生成 CMSG_AUITH_SESSION 数据包,发送给服务端。这个过程是没有经过加密的。
- 客户端发送SMSG_AUTH_SESSION到服务器
- 服务器处理SMSG_AUTH_SESSION
- 服务器发送SMSG_AUTH_RESPONSE给客户端
- 服务器发送SMSG_ADDON_INFO给客户端
- 服务器发送SMSG_CLIENTCACHE_VERSION给客户端
- 服务器发送SMSG_TUTORIAL_FLAGS给客户端
packet结构
SMSG_AUTH_SESSION 是client packet有一个头部(ClientPktHeader),后面是数据块。
SMSG_AUTH_RESPONSE 是server packet有一个头部(ServicePktHeader),后面是数据块。
SMSG_AUTH_RESPONSE 的包组织
SMSG_AUTH_RESPONSE 的opcode是01EE,ByteBuffer大小为1 + 4 + 1 + 4 + 1=11。一个SMSG_AUTH_RESPONSE 的数据如下:
- 8:AUTH_OK
- 32:BillingTimeRemaining
- 8:BillingPlanFlags
- 32:BillingTimeRested
- 8:0 - normal, 1 – TBC
在构造了SMSG_AUTH_RESPONSE packet后,WorldSocket::SendPacket会根据SMSG_AUTH_RESPONSE packet构造出一个ServerPktHeader,并对ServerPktHeader中的数据header进行加密发送。加密采用m_Crypt.EncryptSend ((uint8*)header.header, header.getHeaderLength());
ServerPktHeader |
WorldSocket::handle_input_header会对从客户端接收来的数据进行解密,解密采用m_Crypt.DecryptRecv ((uint8*) m_Header.rd_ptr (), sizeof (ClientPktHeader));
角色枚举
- 玩家登上服务器后,从客户端发送SMSG_CHAR_ENUM到服务器。
- 在服务器端
- 根据{ "CMSG_CHAR_ENUM", STATUS_AUTHED, &WorldSession::HandleCharEnumOpcode },服务器会调用WorldSession::HandleCharEnumOpcode
- WorldSession::HandleCharEnumOpcode会调用CharacterDatabase.AsyncPQuery,进行异步查询后,调用CharacterHandler::HandleCharEnumCallback
- CharacterHandler::HandleCharEnumCallback会回头调用session->HandleCharEnum(result);
- WorldSession::HandleCharEnum根据characters数据库的查询结果,调用Player::BuildEnumData,加载角色数据,构造SMSG_CHAR_ENUM返回包,然后发送回客户端。
角色创建 角色的初始化装备在CharStartOutfit.dbc 角色的创建和选择的设置都在 ChrRaces.dbc 角色的创建属性都在Playercreateinfo,包括出生地和出生属性。 playercreateinfo_item表是创建一个新人物时,人物默认带的所有Item的表 playercreateinfo_spell表是创建一个新人物时,人物默认带的所有Spell的表 角色删除 角色进入游戏 发出的消息有: SMSG_SET_PROFICIENCY SMSG_CRITERIA_UPDATE MSG_DUGEON_DIFFICULTY SMSG_LOGIN_VERIFY_WORLD SMSG_ACCOUNT_DATA_TIMES SMSG_FEATURE_SYSTEM_STATUS SMSG_MOTD SMSG_LEARNED_DANCE_MOVES SMSG_BIND_POINT_UPDATE SMSG_TALENTS_INFO SMSG_INSTANCE_DIFFICULTY SMSG_INITIAL_SPELLS SMSG_UNLEARN_SPELLS SMSG_ACTION_BUTTONS SMSG_ACTION_FACTIONS SMSG_INIT_WORLD_STATES SMSG_EQUIPMENT_SET_LIST SMSG_ALL_ACHIEVEMENT_DATA SMSG_LOGIN_SETTIMESPEED SMSG_TRIGGER_CINEMA SMSG_COMPRESSED_UPDATE_OBJECT: 会发送Monster数据 SMSG_MONSTER_MOVE:会发送Monster的移动数据 SMSG_INIT_WORLD_STATES SMSG_WEATHER SMSG_TIME_SYNC_REQUEST SMSG_WEATHER SMSG_SPELL_GO SMSG_AURA_UPDATE_ALL SMSG_UPDATE_ACCOUNT_COMPLETE SMSG_PLYAED_TIME SMSG_NAME_QUERY_RESPONSE SMSG_RAID_INSTANCE_INFO SMSG_QUERY_TIME_RESPONSE SMSG_GMTICKET_GETTICKET SMSG_QUERY_TIME_RESPONSE SMSG_QUERY_NEXT_MAIL_TIME SMSG_CALENDAR_SEND_NUM_PENDING SMSG_WORLD_STATE_UI_TIMER_UPDATE SMSG_STANDSTATE_UPDATE // 多个 SMSG_CHANNEL_NOTIFY // 多个 SMSG_USERLIST_ADD // 多个
看上面的Mangos的Call Stack,可以明白角色进入游戏场景时,Mangos是如何处理的。具体如下: WorldSession::HandlePlayerLogin:
Map::Add(Player *player) 非常重要。可以知道Mangos是如何按Cell加载地图数据,包括地图上的物品,生物的。WOW客户端会把一些Cell数据缓冲到World of Warcraft\Cache\WDB中。 Map::Add(Player *player) bool Map::Add(Player *player) // update player state for other player and visa-versa SendInitSelf(player); // 把Player信息(transport,EquipmentSlots信息,BankBagSlots信息,KeyRingSlots信息和CurrencyTokenSlots等)以SMSG_UPDATE_OBJECT发送到客户端 UpdatePlayerVisibility(player,cell,p); AddNotifier(player,cell,p); 函数LoadMapAndVMap:按GRID加载map & vmap。 类ObjectGridLoader:按GRID加载Grid上的Objects。在ObjectGridLoader::LoadN中,会生成GridLoader<Player, AllWorldObjectTypes, AllGridObjectTypes> loader,然后调用loader.Load对gird中的Objects进行加载。最终loader.Load会调用到objmgr.GetCellObjectGuids,根据mapid和cell_id得到CellObjectGuids,然后根据CellObjectGuids加载该cell上的objects。参考:CellObjectGuids const& cell_guids = objmgr.GetCellObjectGuids(i_map->GetId(), i_map->GetSpawnMode(), cell_id); 看CellObjectGuids结构 struct CellObjectGuids 可以知道在一个cell中管理了该cell中的所有creatures和gameobjects和corpses。ObjectMgr中的MapObjectGuids mMapObjectGuids就是按照Cell管理所有的所有creatures和gameobjects和corpses。 在ObjectMgr中的mMapObjectGuids结构是:<mapid, spawnMode> <cell_id, < creatures, gameobjects, corpses> > void Map::SendInitSelf( Player * player ) UpdateData data; // attach to player data current transport data // build data for self presence in world at own client (one time for map) // build other passengers at transport also (they always visible and marked as visible and will not send at visibility update at add to map WorldPacket packet; void Map::UpdateObjectsVisibilityFor( Player* player, Cell cell, CellPair cellpair ) cell.data.Part.reserved = ALL_DISTRICT; // send data void Creature::SendMonsterMoveWithSpeedToCurrentDestination(Player* player) void Unit::SendMonsterMoveWithSpeed(float x, float y, float z, uint32 transitTime, Player* player) 怪物的移动 SMSG_MONSTER_MOVE InstanceMap:Dungeon Instance Maps BattleGroundMap:Battleground Instance Maps
一个Map的Grid有4个状态,分别是:
Map::Update |
WDB文件 (参考:http://www.sourcepeek.com/wiki/WDB_Files)
参考:
|