魔兽服务器学习-笔记(服务器部署、地图管理、DB、日志模块、任务模块、战斗模块)

一、环境准备

1)依赖安装

sudo apt-get update
sudo apt-get install git clang cmake gcc g++ 
	libmysqlclient-dev libss-dev libbz2-dev libreadline-dev
	libncurses-dev libboost-all-dev mysql-server-5.7 p7zip

sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang 100
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang 100

//
sudo yum install -y clang 


2)源码下载和编译

git clone -b 3.3.5 https//github.com//TrinityCore/TrinityCore.git

mkdir build cd build
cmake ../ -DCMAKE_INSTALL_PREFIX=/home/lighthouse/tinycore  
-DCONF_DIR=/home/lighthouse/tinycore/bin
make -j2  (nproc看核心数来编译)
make install

二、生成数据信息

1)地图数据信息(客户端信息)

1)cd ~
2)mkdir res  (根目录创建res文件夹)
3)把客户端目录的Data和Interface移动到res目录下

4)cd res
在res目录执行游戏目录bin/下面的mapextractor,
生成dbc和maps文件夹

5)mkdir vmaps
在res目录执行游戏目录bin/下面的vmap4extractor,
生成vmaps文件夹和Buildings目录

5)在res目录执行游戏目录bin/下面的vmap4assembler,
../lighthouse/bin/vmap4assembler Buildings vmaps

6) mkdir mmaps
在res目录执行游戏目录bin/下面的mmaps_generator
生成mmaps目录

2)数据库信息

  • 注意
    需要提前安装mysql
mysql -uroot -p password

1)在TrinityCore的sql/create目录的路径赋值,打开mysql

mysql> source ../Trinity/sql/create/create_mysql.sql

show database;

可以看到生成了auth \ character \ world

三、启动服务器

1)先进到项目的bin目录,复制authserver.conf

启动授权服务器
./authserver

2)同样步骤,复制worldserver.conf
并且改写DataDir,指定res目录

DataDir="../../res"

启动worldserver

启动授权服务器
./worldserver

四、日志模块

五、数据库模块

六、场景模块

1)地图管理

①哪些模块会用到地图模块?
1)运动模块:走、跳、飞行
2)副本
3)寻路
②地图模块要实现哪些功能?
1)AOI:管理地图地理信息、地图对象信息
2)功能:视野、数据同步、碰撞检测、寻路算法
③怎么驱动地图模块?
1)移动的网络数据驱动
2)定时更新(怪物的AI行为)

2)AOI算法

职责:
①静态数据:
《1》trinity由mapextractor生成.map文件(基础地图信息),数据包括

1) area data  (区域物体信息)
2) height data (高度信息)
3) liquid data (水)
4) hole data  (洞)

《2》由mmaps_generator生成可移动地图信息 .mmap,游戏中的地图移动数据(用来给navmesh寻路),也就是用recast和detour生成寻路信息

1) mmtile 索引对应具体地图的所有信息(x+y做名字前缀)
2) .mmap  索引

《3》vmapsextractor生成地图元素信息(可视场景信息:山脉、水体、建筑物等静态场景信息),用于未来做碰撞检测

*.m2和	*.wmo文件  静态物品包围盒信息
*.mdx  角色、物品和怪物包围盒信息

《4》vmap4assembler合并vmapsextractor和mmaps_generator生成的地图信息,vmtile信息

②动态数据

1、静态数据讲解
NGridType* i_grids[MAX_NUMBER_OF_GRIDS][MAX_NUMBER_OF_GRIDS];   
//NGridType是动态数据
GridMap*   GridMaps[MAX_NUMBER_OF_GRIDS][MAX_NUMBER_OF_GRIDS]; //原始数据,最新的已去除
//静态数据,64*64的格子,
//每个格子是8*8个cell,grid和cell都有自己的坐标系
//动物、角色、怪物都是在具体的cell当中 
std::bitset<TOTAL_NUMBER_OF_CELLS_PER_MAP*TOTAL_NUMBER_OF_CELLS_PER_MAP> marked_cells;
2、动态数据:

玩家在哪个grid的cell,哪个grid才会被加载,否则不会被加载到内存当中 (只有玩家进入这个区域才会动态加载这个区域的资源)

3、动态数据结构NGrid解析

①数据结构

//NGridType是模板类
extern template class NGrid<MAX_NUMBER_OF_CELLS, Player, AllWorldObjectTypes, AllGridObjectTypes>;
typedef NGrid<MAX_NUMBER_OF_CELLS, Player, AllWorldObjectTypes, AllGridObjectTypes> NGridType;

AllWorldObjectTypes:(动态的obj)
player玩家、宠物、尸体、动态物品(远处target)
typedef TYPELIST_4(Player, Creature/*pets*/, Corpse/*resurrectable*/, DynamicObject/*farsight target*/) 
AllWorldObjectTypes;

AllGridObjectTypes :(静态的obj)
游戏obj、除了宠物的obj、动态物体、尸体
typedef TYPELIST_7(GameObject, Creature/*except pets*/, DynamicObject, Corpse/*Bones*/, AreaTrigger, SceneObject, Conversation) AllGridObjectTypes;

②具体分析
NGrid有两个容器:i_container和i_objects

    private:
        uint32 i_gridId;
        GridInfo i_GridInfo;
        GridReference<NGrid<N, ACTIVE_OBJECT, WORLD_OBJECT_TYPES, GRID_OBJECT_TYPES> > i_Reference;
        int32 i_x;
        int32 i_y;
        grid_state_t i_cellstate;
        GridType i_cells[N][N];
        bool i_GridObjectDataLoaded;

typedef Grid<Player, AllWorldObjectTypes, AllGridObjectTypes> GridType;
class Grid
    private:

        TypeMapContainer<GRID_OBJECT_TYPES> i_container;
        TypeMapContainer<WORLD_OBJECT_TYPES> i_objects;

玩家进入某个grid的cell,地图加载数据激活推动消息推送

4、涉及用户登录的逻辑及grid状态转变
MapRefManager m_mapRefManager;//MapRefManager双向循环链表


1)根据地图ID、xyz轴、面向在地图上准备new obj
2)把玩家链接到这个地图对应的双向循环链表上
3)根据玩家坐标创建一个cell,加载玩家到对应地图的内存上,若这个cell只有一个玩家则加载cell全部地图信息
4)把玩家的包围盒和怪物、NPC、玩家用BSP tree包围盒做碰撞检测(访问者模式)
DynamicMapTree _dynamicTree;
        void Balance() { _dynamicTree.balance(); }
5、涉及玩家退出的逻辑及grid状态转变
1)当定时器超时玩家没重连,那就把玩家从当前地图清除

⑤地图的状态

1)Idle
2)Active
3)InValid  //无效状态
4)REMOVAL //准备从内存中卸载
  • 地图状态转换说明:
    ①起伏创建是Invalid,
    ②加载具体cell到grid里面是Idle状态,
    ③当把玩家放进来的时候是Active状态,
    ④当玩家退出或掉线时候,这个cell是无人的时候,变为Idle状态
    ⑤当超时重连结束,玩家被从地图删除实例,则是Removal状态,下次服务器tick触发的时候把地图从内存卸载
    (魔兽主线程等待子线程update地图事件,全部结束后才往下把所有map走delayUpdate,更新地图状态机,走从内存卸载删除逻辑)
typedef enum
{
    GRID_STATE_INVALID = 0,
    GRID_STATE_ACTIVE = 1,
    GRID_STATE_IDLE = 2,
    GRID_STATE_REMOVAL= 3,
    MAX_GRID_STATE = 4
} grid_state_t;

3)地图数据驱动

玩家进入某个grid的cell,地图加载数据激活推动消息推送

1)网络驱动

在这里插入图片描述

  • 网络结构说明
    ①创线程池A,线程主要做:
1)信号处理
2)远程连接请求
3)数据库心跳
4)生成core检查
5)异步日志
...

②主线程有主循环,按照一定频率update游戏世界逻辑,执行的逻辑比如

1)更新玩家session信息,根据玩家连接状态更改session状态
(如果不是线程安全,那就在主线程处理,后面的业务逻辑是多线程处理的)
2)主线程处理的逻辑包括:从数据库获取基础的玩家初始数据比如背包等
3)其他线程中的处理:在地图中攻击、寻路、走路

③线程池B,主要处理地图数据
读取配置中的线程数量,创建线程池,并给每条线程发更新地图的放到阻塞的任务队列,每条map也能处理来自客户端发送的数据包

void MapManager::Update(uint32 diff)
{
    i_timer.Update(diff);
    if (!i_timer.Passed())
        return;

    MapMapType::iterator iter = i_maps.begin();
    while (iter != i_maps.end())
    {
        if (iter->second->CanUnload(diff))
        {
            if (DestroyMap(iter->second))
                iter = i_maps.erase(iter);
            else
                ++iter;

            continue;
        }

        if (m_updater.activated())
            m_updater.schedule_update(*iter->second, uint32(i_timer.GetCurrent()));
        else
            iter->second->Update(uint32(i_timer.GetCurrent()));

        ++iter;
    }
    if (m_updater.activated())
        m_updater.wait();

    for (iter = i_maps.begin(); iter != i_maps.end(); ++iter)
        iter->second->DelayedUpdate(uint32(i_timer.GetCurrent()));

    i_timer.SetCurrent(0);
}

④线程池C用来负载均衡处理客户端发包(Network)

2)地图定时更新做的事

①更新这个地图中的玩家,用访问者模式给玩家广播周围的obj(让自己在同cell里面被其他人看到),处理视野相关内容
②包围盒_dynamicTree二叉树检测物体碰撞(惰性更新,每次更新的时候进行平衡)

void Map::Update(uint32 t_diff)
{
    _dynamicTree.update(t_diff); //包围盒检测
    /// update worldsessions for existing players
    for (m_mapRefIter = m_mapRefManager.begin(); m_mapRefIter != m_mapRefManager.end(); ++m_mapRefIter)
    {
        Player* player = m_mapRefIter->GetSource();
        if (player && player->IsInWorld())
        {
            //player->Update(t_diff);
            WorldSession* session = player->GetSession();
            MapSessionFilter updater(session);
            session->Update(t_diff, updater);
        }
    }
    ...
}

③处理人物的重生计时

    /// process any due respawns
    if (_respawnCheckTimer <= t_diff)
    {
        ProcessRespawns();
        UpdateSpawnGroupConditions();
        _respawnCheckTimer = sWorld->getIntConfig(CONFIG_RESPAWN_MINCHECKINTERVALMS);
    }
    else
        _respawnCheckTimer -= t_diff;

④玩家战斗中技能释放、副本流程、人物、成就的更新

4)动态数据管理

(1)设计模式-访问者模式及代码表现形式
  • 概念
    ①提供一个访问者类,可以改变元素类的执行算法
    ②传入不同的访问者,可以使用不同的执行算法
  • 解决的问题
    稳定的数据结构和容易改变的操作耦合问题
  • 解决方法
    在元素类型中提供对应不同访问者的接口
  • 目的
    将数据结构和数据操作进行分离
  • 代码
    T是算法,CONTAINER是数据结构
1)TypeContainer.h  
有两种结构:双向链表和散列表,解决不同链表、不同散列表的差异
2)TypeContainerFunctions.h 
解决双向链表和散列表之间的差异
3)TypeCOntainerVistor.h 
解决对访问者的抽象,不同访问者实现不同的算法

template<class T, class CONTAINER>
inline void Map::Visit(Cell const& cell, TypeContainerVisitor<T, CONTAINER>& visitor)
{
    const uint32 x = cell.GridX();
    const uint32 y = cell.GridY();
    const uint32 cell_x = cell.CellX();
    const uint32 cell_y = cell.CellY();

    if (!cell.NoCreate() || IsGridLoaded(GridCoord(x, y)))
    {
        EnsureGridLoaded(cell);
        getNGrid(x, y)->VisitGrid(cell_x, cell_y, visitor);
    }
}

通过下面这个函数将不同obj加入到不同的双向链表里面

1)不同双向链表:
        TypeMapContainer<GRID_OBJECT_TYPES> i_container;
        TypeMapContainer<WORLD_OBJECT_TYPES> i_objects;



2)访问者算法加入不同链表的算法
template<class OBJECT>
class GridReference : public Reference<GridRefManager<OBJECT>, OBJECT>
{
    protected:
        void targetObjectBuildLink() override
        {
            // called from link()
            this->getTarget()->insertFirst(this);
            this->getTarget()->incSize();
        }
        void targetObjectDestroyLink() override
        {
            // called from unlink()
            if (this->isValid()) this->getTarget()->decSize();
        }
        void sourceObjectDestroyLink() override
        {
            // called from invalidate()
            this->getTarget()->decSize();
        }
    public:
        GridReference() : Reference<GridRefManager<OBJECT>, OBJECT>() { }
        ~GridReference() { this->unlink(); }
        GridReference* next() { return (GridReference*)Reference<GridRefManager<OBJECT>, OBJECT>::next(); }
};


3)不同类型的访问者做不同处理,visit就是不同的算法
class TC_GAME_API ObjectGridLoader : public ObjectGridLoaderBase
{
    friend class ObjectWorldLoader;

    public:
        ObjectGridLoader(NGridType& grid, Map* map, Cell const& cell)
            : ObjectGridLoaderBase(grid, map, cell)
            { }

        void Visit(GameObjectMapType &m);
        void Visit(CreatureMapType &m);
        void Visit(AreaTriggerMapType &m);
        void Visit(CorpseMapType &) const { }        //尸体obj
        void Visit(DynamicObjectMapType&) const { }  //动态物品,掉落装备等
        void Visit(SceneObjectMapType&) const { }    //场景对象
        void Visit(ConversationMapType&) const { }   //对话obj

        void LoadN();
};

5)碰撞检测实现(BIH+AABB)

  • 碰撞检测目标
    对象模型的包围盒
  • 数据来源
    vmap4extractor提取的场景信息(建筑、山脉、水体等)、角色、怪物、装备等3d模型信息
  • 逻辑流程
    1)是否在视线范围内
    2)碰撞检测,算命中位置(如不同楼层等高度信息)
    3)获取地图信息
  • 怎么实现?(一句话:射线和包围盒的碰撞)
    ①插入的时候会平衡一次
    ②然后就是定时200ms平衡一次
  • 实现代码
class TC_COMMON_API DynamicMapTree
{
    DynTreeImpl *impl;

public:

    DynamicMapTree();
    ~DynamicMapTree();

    bool isInLineOfSight(G3D::Vector3 const& startPos, G3D::Vector3 const& endPos, PhaseShift const& phaseShift) const;
    bool getIntersectionTime(G3D::Ray const& ray, G3D::Vector3 const& endPos, PhaseShift const& phaseShift, float& maxDist) const;
    bool getObjectHitPos(G3D::Vector3 const& startPos, G3D::Vector3 const& endPos, G3D::Vector3& resultHitPos, float modifyDist, PhaseShift const& phaseShift) const;

	//看层高
    float getHeight(float x, float y, float z, float maxSearchDist, PhaseShift const& phaseShift) const;
    bool getAreaInfo(float x, float y, float& z, PhaseShift const& phaseShift, uint32& flags, int32& adtId, int32& rootId, int32& groupId) const;
    void getAreaAndLiquidData(float x, float y, float z, PhaseShift const& phaseShift, uint8 reqLiquidType, VMAP::AreaAndLiquidData& data) const;

	//添加、删除、看是否存在
    void insert(GameObjectModel const&);
    void remove(GameObjectModel const&);
    bool contains(GameObjectModel const&) const;

	//更新就是平衡
    void balance();
    void update(uint32 diff); //定时平衡,200ms平衡一次
};

  • 核心算法和数据结构
    数据结构是BIH树,核心算法是AABB(轴对称边界盒算法,射线和包围盒相交的算法),BIH作为二叉树是为了快速进行流程,避免费时计算
    在这里插入图片描述
  • 如何实现BIH树?
    1、构建BIH树
    2、光线追踪算法AABB(AABB并不是稳定的结构,可能随时会有新obj进来)
  • 碰撞计算逻辑
    1、计算包围盒(vmap数据中进行加载)
    2、在一个grid中所有对象根据中轴线构建二叉树,左边为左子树,右边为右子树,判断节点方块与光线是否相交,若相交则不断递归下去,直到满足递归停止条件(递归深度64)
    3、注意:每个节点都是子树对象包围盒的并集
    4、射线就是行走路径
    在这里插入图片描述
  • 优化
    ①初始时构建一次
    ②定时构建:

1、200ms构建一次
2、如果BIH树发生改变才会重新构建

七、战斗模块

  • 总体评价
    分为skill和spell模块

1)技能模块

  • 技能枚举
enum SpellRangeFlag
{
    SPELL_RANGE_DEFAULT             = 0,
    SPELL_RANGE_MELEE               = 1,     //melee,近战
    SPELL_RANGE_RANGED              = 2      //hunter range and ranged weapon,远程
};
①技能信息类SpellInfo
class TC_GAME_API SpellInfo
{
    friend class SpellMgr;

    public:
        uint32 Id;
        SpellCategoryEntry const* CategoryEntry;
        uint32 Dispel;
        uint32 Mechanic;
        uint32 Attributes;
        ........

八、MMO常用模块(任务、副本、商店等)

九、AI

文章记录在魔兽AI解析

十、游戏实体代码

  • 代码细节
1)基类构造函数放在protected里面保证了该类的实例不能被直接创建,只能通过继承该类的子类来创建实例

1)trinitycore实体类介绍

①Unit:表示游戏中的各种实体,例如玩家、NPC、怪物等。Unit是一个抽象的概念,包含了所有实体共有的属性和行为,例如位置、朝向、生命值、法力值、攻击力、防御力等。与Object、WorldObject和GameObject类不同的是,Unit类更加关注实体的战斗属性和行为,例如攻击、防御、技能等。
②Creature:表示游戏中的怪物,是从Unit类继承而来的。Creature类包含了各种怪物特有的属性和行为,例如怪物类型、等级、掉落物品等。
③Player:表示游戏中的玩家,也是从Unit类继承而来的。Player类包含了各种玩家特有的属性和行为,例如职业、技能、装备等。

④Object类:是所有游戏实体类的基类,包含了所有实体共有的属性和行为,例如位置、朝向、生命值、法力值、攻击力、防御力等。与Unit、WorldObject和GameObject类不同的是,Object类更加关注实体的基本属性和行为,例如位置、朝向、生命值、法力值等。

⑤WorldObject类:是从Object类继承而来的类,表示游戏中的所有可视实体,例如玩家、NPC、怪物、游戏对象等。与Unit、Object和GameObject类不同的是,WorldObject类更加关注实体的可视化属性和行为,例如模型、动画、光照等。

⑥GameObject类:是从WorldObject类继承而来的类,表示游戏中的游戏对象,例如门、箱子、火堆等。与Unit、Object和WorldObject类不同的是,GameObject类更加关注实体的对象属性和行为,例如对象类型、状态、交互方式等。

2)分开介绍各实力类

(1)object类

十、背包模块

这里的代码解析我放到别的文章中去了
背包模块代码解析