爬塔流程设计

爬塔流程设计

模式定义

  • 模式id: lm_roguelike=98
  • 游戏模式: game_model_roguelike=17
  • 分页: 3

爬塔effect类型定义

// 增益效果类型
enum emRoguelikeEffectType
{
    Effect_None = 0,                // 啥也没有
    Effect_Hp = 1,                    // 武将体力
    Effect_Coin = 2,                // 金币
    Effect_Card_InitNum = 3,        // 初始手牌数目
    Effect_SQK_UseNum = 4,            // 每局手气卡可使用次数
    Effect_Spell_ExCnt = 5,            // 额外技能槽数目
    Effect_Card_Limit = 6,            // 手牌上限
    Effect_Exp = 7,                    // 经验
    Effect_Spell = 8,                // 技能
    Effect_Buff = 9,                // 祝福,buff
    Effect_Card = 10,                // 牌库牌
    Effect_Equip = 11,                // 装备牌
    Effect_Fight = 12,                // 战斗

    // 以下为服务端使用,客户端暂时用不到
    Effect_Power = 20,                // 模式体力
    Effect_Score = 21,                // 积分
};

爬塔事件类型定义

// 爬塔事件类型
enum emRoguelikeEventType
{
    RoguelikeEvent_Unknow = 0,     // 未知事件
    RoguelikeEvent_Fight = 1,      // 战斗事件
    RoguelikeEvent_Normal = 2,     // 通常事件
    RoguelikeEvent_Select = 3,     // 自选事件
    RoguelikeEvent_Shop = 4,       // 商城事件

};

爬塔商城分页类型定义

enum emShopTab
{
    Tab_Spell = 1,  // 技能
    Tab_Buff = 2,  // buff
    Tab_Equip = 3,  // 装备
    Tab_Card = 4,  // 手牌
};

爬塔数据读取与保存

  • 玩家登录成功后,会从redis中获取爬塔数据,检查赛季切换、体力更新、章节完成情况;
  • 玩家下线时,如果有数据变更,则保存最新数据到redis;

爬塔体力更新

  • 每天会更新一次玩家的爬塔体力值,如果玩家爬塔体力值小于A,则设置玩家爬塔体力值等于A,A配置可配;
  • 可以通过购买对应的道具(9060601)提升爬塔体力值;

技能与装备获取

  • 技能与装备有上限;
  • 额外技能槽数目为0,则不能选择额外的技能;普通事件选择技能,提示无法选择并移除事件;自选事件和等级提升选择技能,提示无法选择技能需要重新选择;
  • 有坑时先填坑,无坑可填时通知替换;

爬塔积分

  • 对局、获得金币、额外技能等影响积分的计算,看配置;

  • 爬塔一轮结束后,会进行爬塔结算,计算积分;如果失败,积分不记录;爬塔成功,如果当前积分大于本赛季的最高积分, 则将本赛季的最高积分置为当前积分,并计入排行榜;

武将星级

  • 武将星级会给武将带来额外的effect加成,看配置;
  • 确认武将后,这一轮爬塔过程中星级不会变化;

爬塔困难度

  • 初始开放困难度为1;
  • 每通关一次, 开放困难度+1;
  • 困难度会有对boss的effect加成和对爬塔挑战者人的effect削弱;

爬塔模式入口数据

  • 客户端进入爬塔模式分页后,主动请求爬塔入口数据;
  • 回复内容里的hasStart表示是否有未完成的章节,客户端可根据这个来判断是否显示继续征程;
  • 回复内容里的roundWaitComplete=true表示章节已完成,但是还没有进行结算,客户端可根据这个来提示玩家进入继续征程并结算;
ABROAD_ROGUELIKE_MODE_DATA_REQ = 48500,   
struct ClientRoguelikeModeDataReq : public PacketBase
{
    ClientRoguelikeModeDataReq() :PacketBase(ABROAD_ROGUELIKE_MODE_DATA_REQ, sizeof(ClientRoguelikeModeDataReq))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeModeDataReq));
    }
};

ABROAD_ROGUELIKE_MODE_DATA_REP= 48501,
struct ClientRoguelikeModeDataRep : public PacketBase
{
    enum emResult
    {
        ok = 0,
        failed = 1,
        not_open = 2, // 活动未开
        data_error = 3, // 数据异常,收到该错误请返回模式准备界面并请求ABROAD_ROGUELIKE_MODE_DATA_REQ
    };

    unsigned int result;
    unsigned int season;  // 赛季id
    unsigned int power;    // 体力值
    bool hasStart;     // 是否有未完成的章节,是否有继续征程
    bool roundWaitComplete;     // 已有征程是否等待结算
    ClientRoguelikeModeDataRep() :PacketBase(ABROAD_ROGUELIKE_MODE_DATA_REP, sizeof(ClientRoguelikeModeDataRep))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeModeDataRep));
    }
};

开始新征程

  • 开始新征程,玩家数据除了赛季id、爬塔体力值、当前赛季最高上榜积分保留,其他数据全部清除;
  • 如果有继续征程或有结算未处理,选择新征程会将这些数据全部清空;
  • 开始新征程,会减一点爬塔体力值,成功后返回可选武将信息和爬塔开放的困难度;
  • 回复的武将列表里的武将都是可以选择的,里面包括玩家拥有的和未拥有的;
  • 武将信息根据武将以及武将星级计算得出,困难度不参与计算
// 可选武将数据
struct RoguelikeChrData
{
    unsigned int chrId;   // 武将id
    unsigned int have;  // 是否拥有武将 1:拥有,0:没有
    unsigned int chrStar; // 星级
    unsigned int coin; // 初始金币
    unsigned int cardInit; // 初始手牌数
    int cardLimit; // 手牌上限增量值
    unsigned int hpInit; // 初始血量
    unsigned int hpLimit; // 血量上限
    unsigned int sqkUse; // 手气卡每局可使用次数
    unsigned int param[2];  // 备用
};

ABROAD_ROGUELIKE_START_REQ=48502
struct ClientRoguelikeStartReq : public PacketBase
{
    ClientRoguelikeStartReq() :PacketBase(ABROAD_ROGUELIKE_START_REQ, sizeof(ClientRoguelikeStartReq))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeStartReq));
    }
};

ABROAD_ROGUELIKE_START_REP=48503
struct ClientRoguelikeStartRep : public PacketBase
{
    const static unsigned int sc_max_chr_num = 20;

    enum emResult
    {
        ok = 0,
        failed = 1,
        no_power = 2, // 没有体力
        no_chr = 3, // 分將失败
        data_error = 4, // 数据异常,收到该错误请返回模式准备界面并请求ABROAD_ROGUELIKE_MODE_DATA_REQ
    };

    unsigned int result;
    unsigned int power;
    unsigned int difficultyOpen;   // 开放的困难等级,小于等于该困难度可选
    unsigned int chrCount;
    RoguelikeChrData chrs[sc_max_chr_num];  // 可选武将列表
    ClientRoguelikeStartRep() :PacketBase(ABROAD_ROGUELIKE_START_REP, sizeof(ClientRoguelikeStartRep))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeStartRep));
    }
};

继续征程

  • 如果之前已开启新征程并扣除了体力,不管有没有选将,下次进入都可以继续之前的征程;
  • 如果之前新征程没有选将,则继续征程需要进行选将,信息同新征程的回复;
  • 如果回复里的roundWaitComplete=true,表示这一轮已经结束,进去模式后客户端无法操作数据,需要主动请求ABROAD_ROGUELIKE_CHECK_ROUND_OVER进行爬塔结算;
  • 如果已选将且正在进行中,则客户端直接进入模式,并主动请求武将、章节、技能等数据;
ABROAD_ROGUELIKE_CONTINUE_REQ=48504
struct ClientRoguelikeContinueReq : public PacketBase
{
    ClientRoguelikeContinueReq() :PacketBase(ABROAD_ROGUELIKE_CONTINUE_REP, sizeof(ClientRoguelikeContinueReq))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeContinueReq));
    }
};

ABROAD_ROGUELIKE_CONTINUE_REP=48505
struct ClientRoguelikeContinueRep : public PacketBase
{
    enum emResult
    {
        ok = 0,
        failed = 1,
        not_start = 2, // 未开始
        no_chr = 3, // 分將失败
        data_error = 4, // 数据异常,收到该错误请返回模式准备界面并请求ABROAD_ROGUELIKE_MODE_DATA_REQ
    };

    unsigned int result;
    bool roundWaitComplete;     // 已有征程是否等待结算,为true是发送ABROAD_ROGUELIKE_CHECK_ROUND_OVER
    bool needSelectChr;  // 是否需要选择武将, 与roundWaitComplete互斥

    //needSelectChr = true时,以下数据有效
    unsigned int difficultyOpen; // 开放的困难等级,小于等于该困难度可选
    unsigned int chrCount;
    RoguelikeChrData chrs[ClientRoguelikeStartRep::sc_max_chr_num];  // 可选武将列表

    ClientRoguelikeContinueRep() :PacketBase(ABROAD_ROGUELIKE_CONTINUE_REP, sizeof(ClientRoguelikeContinueRep))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeContinueRep));
    }
};

选择武将和困难度

  • 客户端可选择开发困难度一下的难度;
  • 客户端从可选武将中选择一个武将,选择确认后,武将的星级也将确认,即在这一轮,武将星级将不再变化;
  • 选将成功后,实际的一些武将信息可能与选将界面提示的数据有出入,这是把困难度的effect计算进去了;
  • 皮肤效果暂时未做;
  • 选将成功后,会生成事件,客户端在收到成功的回复后需要主动请求武将、章节、技能等数据;
ABROAD_ROGUELIKE_CHR_SELECT_REQ=48506
struct ClientRoguelikeSelectChrReq : public PacketBase
{
    unsigned int chrId;   // 选择的武将
    unsigned int difficulty;  // 选择的困难度
    ClientRoguelikeSelectChrReq() :PacketBase(ABROAD_ROGUELIKE_CHR_SELECT_REQ, sizeof(ClientRoguelikeSelectChrReq))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeSelectChrReq));
    }
};

ABROAD_ROGUELIKE_CHR_SELECT_REP=48507
struct ClientRoguelikeSelectChrRep : public PacketBase
{
    enum emResult
    {
        ok = 0,
        failed = 1,
        not_assign = 2,   // 选择的将不是分配的
        difficulty_invalid = 3, // 困难等级非法
        select_error = 4, // 选择异常
        data_error = 5, // 数据异常,收到该错误请返回模式准备界面并请求ABROAD_ROGUELIKE_MODE_DATA_REQ
    };

    unsigned int result;
    unsigned int difficulty; 
    unsigned int chrId;
    unsigned int skinId; // 武将皮肤,暂无换肤处理,先占一个字段,后续再处理
    unsigned int chrStar;  // 选择武将时,该武将的星级
    ClientRoguelikeSelectChrRep() :PacketBase(ABROAD_ROGUELIKE_CHR_SELECT_REP, sizeof(ClientRoguelikeSelectChrRep))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeSelectChrRep));
    }
};

获取武将数据

ABROAD_ROGUELIKE_CHR_DATA_REQ=48508,
struct ClientRoguelikeChrDataReq : public PacketBase
{
    ClientRoguelikeChrDataReq() :PacketBase(ABROAD_ROGUELIKE_CHR_DATA_REQ, sizeof(ClientRoguelikeChrDataReq))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeChrDataReq));
    }
};

ABROAD_ROGUELIKE_CHR_DATA_REP=48509
struct ClientRoguelikeChrDataRep : public PacketBase
{
    enum emResult
    {
        ok = 0,
        failed = 1,
        not_start = 2,  // 未开始
        not_select = 3, // 没有选将
        data_error = 4, // 数据异常,收到该错误请返回模式准备界面并请求ABROAD_ROGUELIKE_MODE_DATA_REQ
    };

    unsigned int result;
    unsigned int chrId;  // 武将id
    unsigned int skinId; // 武将皮肤,暂无换肤处理,先占一个字段,后续再处理
    unsigned int chrStar;  // 武将星级
    unsigned int coinNum;  // 金币数
    unsigned int spellNum; // 技能槽数目(额外获得的+武将自带的)
    unsigned int difficulty; // 困难度
    unsigned int playerLevel; // 等级
    unsigned int playerExp; // 当前经验值
    unsigned int playerUpExp; // 升级的经验值 playerExp > playerUpExp表示可升级,客户端可主动请求ABROAD_ROGUELIKE_LEVEL_UP_REQ
    unsigned int initCardNum; // 手牌初始数目
    int limitCardNum; // 手牌上限增量值
    unsigned int initHpNum; // 初始体力
    unsigned int limitHpNum; // 体力上限
    unsigned int useSQKNum; // 可使用手气卡次数
    unsigned int score;  // 积分
    ClientRoguelikeChrDataRep() :PacketBase(ABROAD_ROGUELIKE_CHR_DATA_REP, sizeof(ClientRoguelikeChrDataRep))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeChrDataRep));
    }
};

获取技能数据

  • 客户端进入模式主动请求;
  • 技能发生变化时,服务端通知ABROAD_ROGUELIKE_SPELL_DATA_REP;
ABROAD_ROGUELIKE_SPELL_DATA_REQ=48510
struct ClientRoguelikeSpellDataReq : public PacketBase
{
    enum emSpellType
    {
        spell = 0,  // 技能
        buff,  // buff
        card,  // 手牌
        equip, // 装备
    };

    unsigned int spellType;
    ClientRoguelikeSpellDataReq() :PacketBase(ABROAD_ROGUELIKE_SPELL_DATA_REQ, sizeof(ClientRoguelikeSpellDataReq))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeSpellDataReq));
    }
};

ABROAD_ROGUELIKE_SPELL_DATA_REP=48511
struct ClientRoguelikeSpellDataRep : public PacketBase
{
    static const unsigned int sc_max_spell_num = 50;
    enum emResult
    {
        ok = 0,
        failed = 1,
        data_error = 2, // 数据异常,收到该错误请返回模式准备界面并请求ABROAD_ROGUELIKE_MODE_DATA_REQ
    };

    unsigned int result; 
    unsigned int spellType;  // emSpellType

    // 是否有可替换的技能操作,客户端可根据该值决定是否请求技能/装备替换操作 ABROAD_ROGUELIKE_SPELL_GET_SWITCH_REQ
    bool hasSwitch;  

    // spellType=equip时有效 start
    unsigned int weaponId; // 武器牌id
    unsigned int armorId; // 防具牌id
    //spellType=equip时有效 end

    // 额外得到的技能/buff/装备/手牌
    unsigned int selectCount;
    unsigned int selects[sc_max_spell_num];

    // spellType=spell时有效 start
    // 选择的武将拥有的技能
    unsigned int selfCount; 
    unsigned int selfs[sc_max_spell_num];
    // spellType=spell时有效 end

    ClientRoguelikeSpellDataRep() :PacketBase(ABROAD_ROGUELIKE_SPELL_DATA_REP, sizeof(ClientRoguelikeSpellDataRep))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeSpellDataRep));
    }

    friend bostream& operator<<(bostream& bos, const ClientRoguelikeSpellDataRep& t)
    {
        bos << t.result << t.spellType << t.hasSwitch << t.weaponId << t.armorId << t.selfCount;
        for (unsigned int i = 0; i < t.selectCount; i++)
        {
            bos << t.selects[i];
            if (i >= ClientRoguelikeSpellDataRep::sc_max_spell_num) break;
        }
        bos << t.selfCount;
        for (unsigned int i = 0; i < t.selfCount; i++)
        {
            bos << t.selfs[i];
            if (i >= ClientRoguelikeSpellDataRep::sc_max_spell_num) break;
        }
        return bos;
    }
};

获取技能替换数据

  • 目前只有装备区和技能区有上限,达到上限后,如果有新技能需要去替换,服务端通知ABROAD_ROGUELIKE_SPELL_GET_SWITCH_REP;
  • 在替换过程中断线等异常退出,下次登录仍需要提示替换;
  • 客户端可以根据ABROAD_ROGUELIKE_SPELL_DATA_REP的hasSwitch主动请求替换ABROAD_ROGUELIKE_SPELL_GET_SWITCH_REQ;
  • 不需要等待回复,因为可能没有需要替换的
ABROAD_ROGUELIKE_SPELL_GET_SWITCH_REQ=48512
struct ClientRoguelikeSpellGetSwitchReq : public PacketBase
{
    unsigned int spellType;
    ClientRoguelikeSpellGetSwitchReq() :PacketBase(ABROAD_ROGUELIKE_SPELL_GET_SWITCH_REQ, sizeof(ClientRoguelikeSpellGetSwitchReq))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeSpellGetSwitchReq));
    }
};

ABROAD_ROGUELIKE_SPELL_GET_SWITCH_REP=48513
struct ClientRoguelikeSpellGetSwitchRep : public PacketBase
{
    enum emResult
    {
        ok = 0,
        failed = 1,
        no_switch = 2, // 没有调整操作
        data_error = 3, // 数据异常,收到该错误请返回模式准备界面并请求ABROAD_ROGUELIKE_MODE_DATA_REQ
    };

    unsigned int result;
    unsigned int spellType;  // emSpellType
    unsigned int switchFlag;  // 替换组标记

    // 等待替换的技能
    unsigned int waitCount; 
    unsigned int waits[ClientRoguelikeSpellDataRep::sc_max_spell_num];

    // 身上已有的且可被替换的技能
    unsigned int selectCount; 
    unsigned int selects[ClientRoguelikeSpellDataRep::sc_max_spell_num];

    ClientRoguelikeSpellGetSwitchRep() :PacketBase(ABROAD_ROGUELIKE_SPELL_GET_SWITCH_REP, sizeof(ClientRoguelikeSpellGetSwitchRep))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeSpellGetSwitchRep));
    }

    friend bostream& operator<<(bostream& bos, const ClientRoguelikeSpellGetSwitchRep& t)
    {
        bos << t.result << t.spellType << t.switchFlag << t.waitCount;
        for (unsigned int i = 0; i < t.waitCount; i++)
        {
            bos << t.waits[i];
            if (i >= ClientRoguelikeSpellDataRep::sc_max_spell_num) break;
        }
        bos << t.selectCount;
        for (unsigned int i = 0; i < t.selectCount; i++)
        {
            bos << t.selects[i];
            if (i >= ClientRoguelikeSpellDataRep::sc_max_spell_num) break;
        }
        return bos;
    }
};

技能替换操作

  • 注意: 选择武将时,武将自带的技能不可以被替换!
ABROAD_ROGUELIKE_SPELL_DO_SWITCH_REQ=48514
struct ClientRoguelikeSpellDoSwitchReq : public PacketBase
{
    unsigned int spellType; //emSpellType
    unsigned int switchFlag; // 替换组标记

    // 选择的技能/装备
    // 如果是装备,需要将武器牌、防具牌、宝物*2依次填入,无论是否有牌
    // 将选择后身上装备的技能/装备填入selects
    unsigned int selectCount; 
    unsigned int selects[ClientRoguelikeSpellDataRep::sc_max_spell_num]; 
    ClientRoguelikeSpellDoSwitchReq() :PacketBase(ABROAD_ROGUELIKE_SPELL_DO_SWITCH_REQ, sizeof(ClientRoguelikeSpellDoSwitchReq))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeSpellDoSwitchReq));
    }
};

ABROAD_ROGUELIKE_SPELL_DO_SWITCH_REP=48515
struct ClientRoguelikeSpellDoSwitchRep : public PacketBase
{
    enum emResult
    {
        ok = 0,
        failed = 1,
        had_do = 2, // 该替换组已执行过操作
        self_cannot = 3,  // 武将的技能不可以被替换
        data_error = 4, // 数据异常,收到该错误请返回模式准备界面并请求ABROAD_ROGUELIKE_MODE_DATA_REQ
        select_error = 5, // 有重复数据,或者装备替换但没有将武器牌、防具牌、宝物*2一次填入
    };

    unsigned int result;
    unsigned int spellType;
    unsigned int switchFlag;

    ClientRoguelikeSpellDoSwitchRep() :PacketBase(ABROAD_ROGUELIKE_SPELL_DO_SWITCH_REQ, sizeof(ClientRoguelikeSpellDoSwitchRep))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeSpellDoSwitchRep));
    }
};

等级更新

  • 玩家经验值提升会带来等级的提升,等级提升会有额外的effect加成,具体看配置;
  • 等级提升时,如果有需要玩家确认的随机奖励,则需要玩家确认后才会计算effect加成;没有则直接添加effect加成;

等级更新请求

  • 等级升级时,服务端通知ABROAD_ROGUELIKE_LEVEL_UP_REP
  • 客户端可根据ABROAD_ROGUELIKE_CHR_DATA_REP的playerExp >= playerUpExp主动请求ABROAD_ROGUELIKE_LEVEL_UP_REQ
  • 不需要等待回复,因为可能没有升级
ABROAD_ROGUELIKE_LEVEL_UP_REQ=48516
struct ClientRoguelikeLevelUpReq : public PacketBase
{
    ClientRoguelikeLevelUpReq() :PacketBase(ABROAD_ROGUELIKE_LEVEL_UP_REQ, sizeof(ClientRoguelikeLevelUpReq))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeLevelUpReq));
    }
};

ABROAD_ROGUELIKE_LEVEL_UP_REP=48517
// 增益效果
struct TEffectData
{
    // 类型,emRoguelikeEffectType
    unsigned int type;     

    // 参数值
    // 一般只有value[0]有效
    // type=Effect_Hp,  value[0]为增加上限(需支持负值,至多到0);value[1]为体力,可不配,不配默认和value[0]一致,不可大于value[0]值
    // type=Effect_Spell/Effect_Buff/Effect_Card/Effect_Equip,  value[0]为指定技能id
    int value[3];
};

struct ClientRoguelikeLevelUpRep : public PacketBase
{
    static const unsigned int sc_effect_max_num = 10;

    unsigned int oldLevel;  // 原等级
    unsigned int newLevel;  // 升级到的等级

    // 升级必得的奖励
    unsigned int mustCnt; 
    TEffectData mustEffect[sc_effect_max_num];

    // 升级可选择的奖励的个数
    unsigned int randomGetNum; 

    // 升级可选择的奖励
    unsigned int randomCnt; 
    TEffectData randomEffect[sc_effect_max_num];

    ClientRoguelikeLevelUpRep() :PacketBase(ABROAD_ROGUELIKE_LEVEL_UP_REP, sizeof(ClientRoguelikeLevelUpRep))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeLevelUpRep));
    }

    friend bostream& operator<<(bostream& bos, const ClientRoguelikeLevelUpRep& t)
    {
        bos << t.oldLevel << t.newLevel << t.mustCnt;
        for (unsigned int i = 0; i < t.mustCnt; i++)
        {
            bos << t.mustEffect[i].type << t.mustEffect[i].value[0] << t.mustEffect[i].value[1] << t.mustEffect[i].value[2];
            if (i >= sc_effect_max_num) break;
        }
        bos << t.randomGetNum << t.randomCnt;
        for (unsigned int i = 0; i < t.randomCnt; i++)
        {
            bos << t.randomEffect[i].type << t.randomEffect[i].value[0] << t.randomEffect[i].value[1] << t.randomEffect[i].value[2];
            if (i >= sc_effect_max_num) break;
        }
        return bos;
    }
};

等级升级奖励选择

  • 如果武将升级奖励有随机奖励需要玩家确认,则客户端需要进行确认,否则不需要
ABROAD_ROGUELIKE_LEVEL_UP_SELECT_REQ=48518
struct ClientRoguelikeLevelUpSelectReq : public PacketBase
{
    unsigned int toLevel;  // 升级到的等级

    // 选择的奖励
    unsigned int selectCount;
    unsigned int selectRandomIds[ClientRoguelikeLevelUpRep::sc_effect_max_num];
    ClientRoguelikeLevelUpSelectReq() :PacketBase(ABROAD_ROGUELIKE_LEVEL_UP_SELECT_REQ, sizeof(ClientRoguelikeLevelUpSelectReq))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeLevelUpSelectReq));
    }
};

ABROAD_ROGUELIKE_LEVEL_UP_SELECT_REP=48519
struct ClientRoguelikeLevelUpSelectRep : public PacketBase
{
    enum emResult
    {
        ok = 0,
        failed = 1,
        up_already = 2, // 已升级到该等级
        tolevel_invalid = 3,  // 升级的等级不对
        count_invalid = 4, // 选择的随机奖励数目不对
        select_invalid = 5, // 选择的随机奖励不对
        data_error = 6, // 数据异常,收到该错误请返回模式准备界面并请求ABROAD_ROGUELIKE_MODE_DATA_REQ
        no_spell_pos_fill = 7, // 无法选择技能,没有额外技能槽
    };

    unsigned int result;  
    unsigned int toLevel;  // 升级到的等级

    ClientRoguelikeLevelUpSelectRep() :PacketBase(ABROAD_ROGUELIKE_LEVEL_UP_SELECT_REP, sizeof(ClientRoguelikeLevelUpSelectRep))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeLevelUpSelectRep));
    }
};

章节数据请求

  • 客户端进入模式主动请求
ABROAD_ROGUELIKE_CHAPTER_DATA_REQ = 48520
struct ClientRoguelikeChapterDataReq : public PacketBase
{
    ClientRoguelikeChapterDataReq() :PacketBase(ABROAD_ROGUELIKE_CHAPTER_DATA_REQ, sizeof(ClientRoguelikeChapterDataReq))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeChapterDataReq));
    }
};

ABROAD_ROGUELIKE_CHAPTER_DATA_REQ = 48521
struct ClientRoguelikeChapterDataRep : public PacketBase
{
    static const unsigned int sc_max_event_show_num = 10;

    enum emResult
    {
        ok = 0,
        failed = 1,
        not_start = 2, // 未开始
        data_error = 3, // 数据异常,收到该错误请返回模式准备界面
    };

    struct EventData
    {
        unsigned int no;  // 第几个事件
        unsigned int id;   // 事件id,客户端根据id去找配置进行显示
    };

    unsigned int result;
    unsigned int chapterNo;  // 当前第几个章节
    unsigned int eventNo; // 当前章节里第几个事件
    unsigned int eventTotalNum; // 当前章节事件总数目
    unsigned int totalChapterNum; // 章节总数目
    unsigned int showEventNum; // 显示的事件数目
    EventData events[sc_max_event_show_num]; // 显示的事件数据

    ClientRoguelikeChapterDataRep() :PacketBase(ABROAD_ROGUELIKE_CHAPTER_DATA_REP, sizeof(ClientRoguelikeChapterDataRep))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeChapterDataRep));
    }

    friend bostream& operator<<(bostream& bos, const ClientRoguelikeChapterDataRep& t)
    {
        bos << t.result << t.chapterNo << t.eventNo << t.eventTotalNum << t.totalChapterNum << t.showEventNum ;
        for (unsigned int i = 0; i < t.showEventNum; i++)
        {
            bos << t.events[i].no << t.events[i].id;
            if (i >= sc_max_event_show_num) break;
        }
        return bos;
    }
};

商店数据请求

  • 打开商店事件时,需要请求商店的内容数据
ABROAD_ROGUELIKE_SHOP_DATA_REQ = 48527
struct ClientRoguelikeShopDataReq : public PacketBase
{
    unsigned int chapterNo;  // 当前第几个章节
    unsigned int eventNo; // 商城事件序号
    ClientRoguelikeShopDataReq() :PacketBase(ABROAD_ROGUELIKE_SHOP_DATA_REQ, sizeof(ClientRoguelikeShopDataReq))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeShopDataReq));
    }
};

ABROAD_ROGUELIKE_SHOP_DATA_REP=48528
struct ShopItem
{
    unsigned int id;
    unsigned int price;
}
struct ClientRoguelikeShopDataRep : public PacketBase
{
    static const unsigned int sc_max_shoptab_num = 4;
    static const unsigned int sc_max_shopitem_num = 10;

    enum emResult
    {
        ok = 0,
        failed = 1,
        not_start = 2, // 未开始
        eventno_invalid = 3,
        data_error = 4, // 数据异常,收到该错误请返回模式准备界面并请求ABROAD_ROGUELIKE_MODE_DATA_REQ
    };

    struct ShopTab
    {
        unsigned int tab;   // 分页类型
        unsigned int buyCnt; // 购买次数
        unsigned int premium; // 单次购买溢价
        unsigned int count; // 商品数目
        ShopItem items[sc_max_shopitem_num]; // 商品信息
    };

    unsigned int result;
    unsigned int chapterNo;  // 当前第几个章节
    unsigned int eventNo; // 第几个事件
    unsigned int eventId;  // 事件id
    unsigned int tabCount;  // 有几个分页
    ShopTab tabs[sc_max_shoptab_num]; // 分页数据

    ClientRoguelikeShopDataRep() :PacketBase(ABROAD_ROGUELIKE_SHOP_DATA_REQ, sizeof(ClientRoguelikeShopDataRep))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeShopDataRep));
    }

    friend bostream& operator<<(bostream& bos, const ClientRoguelikeShopDataRep& t)
    {
        bos << t.result << t.chapterNo << t.eventNo << t.eventId << t.tabCount;
        for (unsigned int i = 0; i < t.tabCount; i++)
        {
            bos << t.tabs[i].tab << t.tabs[i].buyCnt << t.tabs[i].premium << t.tabs[i].count;
            auto &item = t.tabs[i].items;
            for (unsigned int k = 0; k < t.tabs[i].count; k++)
            {
                bos << item[k].id << item[k].price;
                if (k >= sc_max_shopitem_num) break;
            }
            if (i >= sc_max_shoptab_num) break;
        }
        return bos;
    }
};

商城购买

  • 购买成功后,服务端会移除购买的物品,客户端主要主动请求这个商店的数据,物品价格会有变动;
  • 所有商店的溢价次数通用;
  • 商店的所有物品全部购买后,该商店将没有物品可买,需要客户端主动关闭商店事件;
ABROAD_ROGUELIKE_SHOP_BUY_REQ=48524
struct ClientRoguelikeShopBuyReq : public PacketBase
{
    unsigned int chapterNo; // 当前章节
    unsigned int eventNo; // 第几个事件
    unsigned int tab; // 分页
    unsigned int buyId; // 购买的物品id
    ClientRoguelikeShopBuyReq() :PacketBase(ABROAD_ROGUELIKE_SHOP_BUY_REQ, sizeof(ClientRoguelikeShopBuyReq))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeShopBuyReq));
    }
};

ABROAD_ROGUELIKE_SHOP_BUY_REP=48525
struct ClientRoguelikeShopBuyRep : public PacketBase
{
    enum emResult
    {
        ok = 0,
        failed = 1,
        event_invalid = 2, // 事件非法
        event_not_find = 3, // 事件找不到
        tab_invalid = 4, // 分页非法
        buyid_not_find = 5, // 物品找不到
        coin_not_enough = 6, // 金币不够
        chapter_over = 7,  // 本章节已结束
        data_error = 8, // 数据异常,收到该错误请返回模式准备界面并请求ABROAD_ROGUELIKE_MODE_DATA_REQ
        no_spell_pos_fill = 9, // 无法选择技能,没有额外技能槽
        had_already = 10, // 已拥有
    };

    unsigned int result;
    unsigned int chapterNo;
    unsigned int eventNo;
    unsigned int tab;
    unsigned int buyId;

    ClientRoguelikeShopBuyRep() :PacketBase(ABROAD_ROGUELIKE_SHOP_BUY_REP, sizeof(ClientRoguelikeShopBuyRep))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeShopBuyRep));
    }
};

事件操作请求

ABROAD_ROGUELIKE_EVENT_OP_REQ=48522
struct ClientRoguelikeEventOpReq : public PacketBase
{
    enum emOpType
    {
        op_close = 0, // 关闭移除事件
        op_select, // 选项选择
        op_sure, // 确认
    };

    unsigned int chapterNo;  // 当前章节
    unsigned int eventNo; // 第几个事件
    unsigned int opType; // emOpType
    unsigned int select; // 选择了第几项
    ClientRoguelikeEventOpReq() :PacketBase(ABROAD_ROGUELIKE_EVENT_OP_REQ, sizeof(ClientRoguelikeEventOpReq))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeEventOpReq));
    }
};

ABROAD_ROGUELIKE_EVENT_OP_REP=48523
    struct ClientRoguelikeEventOpRep : public PacketBase
{
    enum emResult
    {
        ok = 0,
        failed = 1,
        event_invalid = 2, // 事件非法
        event_not_find = 3, // 事件找不到
        select_invlid = 4,  // 选择非法
        fight_data_get_failed = 5, // 战斗数据获取失败
        chapter_over = 6, // 本章节已结束
        data_error = 7, // 数据异常,收到该错误请返回模式准备界面并请求ABROAD_ROGUELIKE_MODE_DATA_REQ
        no_spell_pos_fill = 8, // 无法选择技能,没有额外技能槽
    };

    unsigned int result;  
    unsigned int chapterNo;
    unsigned int eventNo;
    unsigned int opType;
    unsigned int select;

    ClientRoguelikeEventOpRep() :PacketBase(ABROAD_ROGUELIKE_EVENT_OP_REP, sizeof(ClientRoguelikeEventOpRep))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeEventOpRep));
    }
};

数据更新通知

ABROAD_ROGUELIKE_VALUE_UPDATE=48526
struct ClientRoguelikeValueUpdateNtf : public PacketBase
{
    static const unsigned int sc_max_update_num = 20;
    enum emType
    {
        update_power = 0, // 进入模式的体力更新
        update_hp,  // 武将体力初始值与上限
        update_coin, // 金币
        update_handcard_num, // 手牌初始数目与上限
        update_spell_ex_num, // 技能槽额外数目
        update_exp, // 经验
        update_sqk_use, // 手气卡使用次数
        update_score, // 积分

        update_max,
    };

    struct UpdateValue
    {
        unsigned int type;
        union
        {
            struct {
                unsigned int value;
            }common;  // update_power/update_coin/update_spell_ex_num/update_sqk_use/update_score

            struct {
                unsigned int init;  // 初始数目
                int limit; // 上限(增量值)
            }hpCard;  // update_hp/update_handcard_num

            struct {
                unsigned int level; // 当前的等级
                unsigned int exp; // 当前的经验值,大于nextUpExp时表示可升级
                unsigned int nextUpExp; // 升级到下一级需要的经验值
            }exp;  // update_exp
        };
    };

    unsigned int count;
    UpdateValue update[sc_max_update_num];

    ClientRoguelikeValueUpdateNtf() :PacketBase(ABROAD_ROGUELIKE_VALUE_UPDATE, sizeof(ClientRoguelikeValueUpdateNtf))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeValueUpdateNtf));
    }

    friend bostream& operator<<(bostream& bos, const ClientRoguelikeValueUpdateNtf& t)
    {
        bos << t.count;
        for (unsigned int i = 0; i < t.count; i++)
        {
            switch (t.update[i].type)
            {
            case update_power:
            case update_coin:
            case update_spell_ex_num:
            case update_sqk_use:
            case update_score:
                bos << t.update[i].type << t.update[i].common.value;
                break;
            case update_hp:
            case update_handcard_num:
                bos << t.update[i].type << t.update[i].hpCard.init << t.update[i].hpCard.limit;
                break;
            case update_exp:
                bos << t.update[i].type << t.update[i].exp.level << t.update[i].exp.exp << t.update[i].exp.nextUpExp;
                break;
            default:
                break;
            }
            if (i >= sc_max_update_num) break;
        }
        return bos;
    }
};

获取boss数据

ABROAD_ROGUELIKE_BOSS_DATA_REQ=48529
struct ClientRoguelikeBossDataReq : public PacketBase
{
    unsigned int chapterNo; 
    unsigned int eventNo;  

    ClientRoguelikeBossDataReq() :PacketBase(ABROAD_ROGUELIKE_BOSS_DATA_REQ, sizeof(ClientRoguelikeBossDataReq))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeBossDataReq));
    }
};

struct RoguelikePlayerData
{
    unsigned int playerType;  // 玩家类型, 0:玩家,1:boss
    unsigned int chrId;  // 武将id
    unsigned int chrStar; // 武将星级(暂时无用)
    unsigned int skinId; // 武将皮肤(暂时无用)
    unsigned int sqkUse; // 每局手气卡可使用次数(暂时无用)
    unsigned int level;  // 等级
    unsigned int seatid; // 座位
    unsigned int figure; // 身份
    unsigned int aiLevel; // ai等级
    unsigned int initCardNum; // 手牌初始数目
    int limitCardNum; // 手牌上限增量值
    unsigned int initHpNum; // 初始体力
    unsigned int limitHpNum; // 体力上限
    unsigned int weaponId; // 武器牌id
    unsigned int armorId; // 防具牌id
    unsigned int treasureIds[2]; //宝物
    unsigned int buffCount; //buff
    unsigned int buffIds[ClientRoguelikeSpellDataRep::sc_max_spell_num];
    unsigned int cardCount; // 手牌
    unsigned int cardIds[ClientRoguelikeSpellDataRep::sc_max_spell_num];
    unsigned int spellCount; // 技能
    unsigned int spellIds[ClientRoguelikeSpellDataRep::sc_max_spell_num];
};

ABROAD_ROGUELIKE_BOSS_DATA_REP=48530
struct ClientRoguelikeBossDataRep : public PacketBase
{
    static const unsigned int sc_max_boss_count = 8;

    enum emResult
    {
        ok = 0,
        failed = 1,
        event_invalid = 2, 
        data_error = 3, // 数据异常,收到该错误请返回模式准备界面并请求ABROAD_ROGUELIKE_MODE_DATA_REQ
    };

    unsigned int result;
    unsigned int chapterNo;
    unsigned int eventNo;
    unsigned int count;
    RoguelikePlayerData boss[sc_max_boss_count];

    ClientRoguelikeBossDataRep() :PacketBase(ABROAD_ROGUELIKE_BOSS_DATA_REP, sizeof(ClientRoguelikeBossDataRep))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeBossDataRep));
    }

    friend bostream& operator<<(bostream& bos, const ClientRoguelikeBossDataRep& t)
    {
        bos << t.result << t.chapterNo << t.eventNo << t.count;
        for (unsigned int i = 0; i < t.count; i++)
        {
            bos << t.boss[i].playerType << t.boss[i].chrId << t.boss[i].chrStar << t.boss[i].skinId << t.boss[i].sqkUse << t.boss[i].level 
                << t.boss[i].seatid << t.boss[i].figure << t.boss[i].aiLevel << t.boss[i].initCardNum << t.boss[i].limitCardNum 
                << t.boss[i].initHpNum << t.boss[i].limitHpNum << t.boss[i].weaponId << t.boss[i].armorId << t.boss[i].treasureIds[0] 
                << t.boss[i].treasureIds[1] << t.boss[i].buffCount;
            for (unsigned int x = 0; x < t.boss[i].buffCount; x++)
            {
                bos << t.boss[i].buffIds[x];
                if (x >= ClientRoguelikeSpellDataRep::sc_max_spell_num) break;
            }
            bos << t.boss[i].cardCount;
            for (unsigned int x = 0; x < t.boss[i].cardCount; x++)
            {
                bos << t.boss[i].cardIds[x];
                if (x >= ClientRoguelikeSpellDataRep::sc_max_spell_num) break;
            }
            bos << t.boss[i].spellCount;
            for (unsigned int x = 0; x < t.boss[i].spellCount; x++)
            {
                bos << t.boss[i].spellIds[x];
                if (x >= ClientRoguelikeSpellDataRep::sc_max_spell_num) break;
            }
            if (i >= sc_max_boss_count) break;
        }
        return bos;
    }
};

检查爬塔一轮是否结束

  • 客户端请求继续征程回复ABROAD_ROGUELIKE_CONTINUE_REP的roundWaitComplete=true时,主动请求ABROAD_ROGUELIKE_CHECK_ROUND_OVER;
  • 如果一轮结束,服务端发送结果ClientRoguelikeResultNtf;
ABROAD_ROGUELIKE_CHECK_ROUND_OVER=48531
struct ClientRoguelikeCheckRoundOver : public PacketBase
{
    ClientRoguelikeCheckRoundOver() :PacketBase(ABROAD_ROGUELIKE_CHECK_ROUND_OVER, sizeof(ClientRoguelikeCheckRoundOver))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeCheckRoundOver));
    }
};

爬塔结果通知

  • 客户端进入游戏模式主动请求ABROAD_ROGUELIKE_CHECK_ROUND_OVER后,可能收到该消息;
  • 客户端收到该消息后,关闭结算界面时注意退出模式(需要重新开始),并请求模式数据;
ABROAD_ROGUELIKE_RESULT_NTF=48532
struct ClientRoguelikeResultNtf : public PacketBase
{
    static const unsigned int sc_max_reward_count = 20;

    struct RewardGood
    {
        unsigned int goodsId;
        unsigned int goodsCount;
    };

    unsigned int season;
    bool isWin;  // 是否胜利
    unsigned int score; // 本轮积分
    // 结算奖励
    unsigned int count;
    RewardGood reward[sc_max_reward_count];

    ClientRoguelikeResultNtf() :PacketBase(ABROAD_ROGUELIKE_RESULT_NTF, sizeof(ClientRoguelikeResultNtf))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeBossDataReq));
    }

    friend bostream& operator<<(bostream& bos, const ClientRoguelikeResultNtf& t)
    {
        bos << t.season << t.isWin << t.score << t.count;
        for (unsigned int i = 0; i < t.count; i++)
        {
            bos << t.reward[i].goodsId << t.reward[i].goodsCount;
            if (i >= sc_max_reward_count) break;
        }
        return bos;
    }
};

爬塔对局结果

  • 爬塔模式对局结束以该协议为准;
  • 爬塔模式对MSG_GAME_OVER=21228的数据不处理;
  • 对局内托管时间超过2分钟,视为逃跑,无论输赢,不记录进度,没有奖励;
  • 对局结束时,玩家离线,玩家游戏时间与对局总时长相差小于30秒,正常结算,大于30s视为逃跑处理;
  • 对局失败,爬塔结束;
  • 从对局内返回对局外的爬塔章节界面时,需要主动请求检查爬塔一轮是否结束ABROAD_ROGUELIKE_CHECK_ROUND_OVER
struct RoguelikeEffectData
{
    unsigned int type;
    unsigned int value;
};

ABROAD_ROGUELIKE_GAME_OVER=48536
struct ClientRoguelikeGameOver : public PacketBase
{
    static const unsigned int sc_max_effect_count = 20;

    unsigned int result; // 对局结果
    bool isRunaway; // 是否是逃跑,true时不对结果进行处理

    // result=0且isRunaway=false时,以下数据有效
    unsigned int count;
    RoguelikeEffectData effects[sc_max_effect_count];

    ClientRoguelikeGameOver() :PacketBase(ABROAD_ROGUELIKE_GAME_OVER, sizeof(ClientRoguelikeGameOver))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRoguelikeGameOver));
    }
};

爬塔对局流程

确认战斗

  • 客户端确认战斗,发送ABROAD_ROGUELIKE_EVENT_OP_REQ -> gt -> dbs;

战斗检查

  • dbs收到战斗请求,检查数据;
  • 检查通过后向cs发送建桌请求ABROAD_ROGUELIKE_CREATE_TABLE_REQ;
  • 检查通过回复客户端ABROAD_ROGUELIKE_EVENT_OP_REP;
// 爬塔对局需要的数据
ABROAD_ROGUELIKE_CREATE_TABLE_REQ=48533
struct DbsCsRoguelikeCreateTableReq : public PacketBase
{
    unsigned int eventNo;   // 第几个事件
    unsigned int fightId;   // 关卡id
    DbsCsRoguelikeCreateTableReq() : PacketBase(ABROAD_ROGUELIKE_CREATE_TABLE_REQ, sizeof(DbsCsRoguelikeCreateTableReq))
    {
        PRO_ZERO_MEMORY(this, sizeof(DbsCsRoguelikeCreateTableReq));
    }
};

cs建桌

  • cs收到ABROAD_ROGUELIKE_CREATE_TABLE_REQ,创建建桌请求CLIENT_CREATETABLE_REQ(20501)并处理;
  • cs回复客户端CLIENT_CREATETABLE_REP(20502),客户端收到建桌成功后,进入对局场景加载界面;收到建桌失败,则提示失败;
  • cs建桌成功后,通知gs建桌CS_GS_USERCREATETABLE_REQ(20522);

gs建桌

  • gs处理建桌请求;
  • gs建桌失败,则踢出玩家在gs下线;
  • gs建桌成功后,加入玩家和AI,然后向dbs请求战斗数据ABROAD_ROGUELIKE_FIGHT_DATA_REQ;
  • gs等待dbs回复,超时未回复则踢玩家下桌并解散桌子;
ABROAD_ROGUELIKE_FIGHT_DATA_REQ=48534
struct GsDbsRoguelikeFightDataReq : public PacketBase
{
    unsigned int eventNo;
    unsigned int fightId;
    GsDbsRoguelikeFightDataReq() : PacketBase(ABROAD_ROGUELIKE_FIGHT_DATA_REQ, sizeof(GsDbsRoguelikeFightDataReq))
    {
        PRO_ZERO_MEMORY(this, sizeof(GsDbsRoguelikeFightDataReq));
    }
};

ABROAD_ROGUELIKE_FIGHT_DATA_REP=48535
struct GsDbsRoguelikeFightDataRep : public PacketBase
{
    static const unsigned int sc_max_effect_count = 20;

    enum emResult
    {
        ok = 0,
        failed,
        event_invalid,
    };

    unsigned int result;
    unsigned int season;
    unsigned int chapterNo;
    unsigned int eventNo;
    unsigned int eventId;
    unsigned int fightId;
    unsigned int difficulty;

    unsigned int effectCount;
    RoguelikeEffectData effects[sc_max_effect_count];

    unsigned int playerCount;
    RoguelikePlayerData player[MAX_TABLE_PLAYER];

    GsDbsRoguelikeFightDataRep() : PacketBase(ABROAD_ROGUELIKE_FIGHT_DATA_REP, sizeof(GsDbsRoguelikeFightDataRep))
    {
        PRO_ZERO_MEMORY(this, sizeof(GsDbsRoguelikeFightDataRep));
    }
};

dbs整理爬塔对局需要的数据

  • dbs收到ABROAD_ROGUELIKE_FIGHT_DATA_REQ,检查数据并回复ABROAD_ROGUELIKE_FIGHT_DATA_REP;

gs处理爬塔对局需要的数据

  • gs收到ABROAD_ROGUELIKE_FIGHT_DATA_REP,数据正确则进入对局流程,否则踢玩家下桌并解散桌子;

初始化游戏牌库

  • 将所有玩家对局外带进来的牌放入虚拟区;
  • 洗牌;

分配角色身份

  • 根据爬塔对局数据,给每个而玩家分配身份;
  • 向玩家展示其他玩家的身份MSG_GAME_SHOW_FIGURE(21223);

分发武将

  • 根据爬塔对局数据,分配武将,设置基础值(血量初始值和上限);
  • 给爬塔挑战者分配武将;
  • 给其他玩家分配武将;

展示对局玩家信息

  • 展示武将信息SMSG_GAME_SET_CHARACTER(21227);
  • 更新血量GS_C_UPDATE_HP_NTF(21216);
  • 添加额外技能与buff GS_C_UPDATE_ROLE_DATA_EX_NTF(21252);

发初始卡牌

  • 根据爬塔对局数据,给每个玩家发放对应数目的初始手牌;
  • 等待玩家使用手气卡;

添加场外牌

  • 根据爬塔对局数据,给每个玩家发放相应的场外牌(手牌、装备);

开始对战

对局内相关协议修改

玩家角色信息通知

  • id添加了新值OPT_DATA_ADD_CHARACTER_BUFF_NEW、OPT_DATA_DEL_CHARACTER_BUFF_NEW、OPT_DATA_SET_CHARACTER_BUFF_NEW

    struct MsgUpdateRoleData_Ex_Ntf : public PacketBase 
    {
      enum OptDataId
      {
          OPT_DATA_INVALID = 0,
          OPT_DATA_ADD_EX_SPELL,          //给role添加技能 data为spell id(袁术-伪帝)
          OPT_DATA_DEL_EX_SPELL,            //给role删除技能 data为spell id(断肠)
          OPT_DATA_IS_FIGURE_TAP,            //身份牌是否横置(public)
          OPT_DATA_IS_CHARCARD_TAP,       //武将牌是否横置(public)
          OPT_DATA_IS_CHARCARD_TURN_OVER, //武将牌是否翻面(public)
          OPT_DATA_REST,                    //重整(public)
          OPT_DATA_LIMITED,                //限定技使用
          OPT_DATA_ADD_SPELL_EFFECT,        //增加技能效果
          OPT_DATA_REMOVE_SPELL_EFFECT,    //删除技能效果
          OPT_DATA_SET_CHARACTER_SPELL,    //设置武将有哪些技能
          OPT_ADD_HUA_SHEN_CARD,            //增加化身牌
          OPT_SET_HUA_SHEN_CARD,            //设置化身牌
          OPT_SET_CHARACTER_COUNTRY,        //设置武将国家
          OPT_SET_CHARACTER_GENDER,        //设置武将性别
          OPT_DATA_ADD_CHARACTER_SPELL_NEW, //给role添加技能 data[0]表示武将ID,为无效值时表示添加到角色,data[1]:添加的技能个数
          OPT_DATA_DEL_CHARACTER_SPELL_NEW, //给role删除技能 data[0]表示武将ID,为无效值时表示从角色身上删除; data[1]:删除的技能个数
          OPT_DATA_SET_CHARACTER_SPELL_NEW, //设置武将技能 data[0]表示武将ID,为无效值时表示设置的是玩家的技能;data[1]:技能个数
          OPT_DATA_SET_COMMON_HUAN_SHEN_CARD,//这一局中常用化身牌
          OPT_DATA_XIAO_BING,    //小兵状态
          OPT_DATA_STAR_PRIVILEGE,
          OPT_DATA_FU_LIN,    // 腹鳞牌
          OPT_DATA_DON_NOT_DISCARD_IN_DISCARD_PHASE,
    
          OPT_DATA_ADD_CHARACTER_BUFF_NEW, //给role添加buff data[0]表示武将ID,为无效值时表示添加到角色,data[1]:添加的buff个数
          OPT_DATA_DEL_CHARACTER_BUFF_NEW, //给role删除buff data[0]表示武将ID,为无效值时表示从角色身上删除; data[1]:删除的buff个数
          OPT_DATA_SET_CHARACTER_BUFF_NEW, //设置武将buff data[0]表示武将ID,为无效值时表示设置的是玩家的buff;data[1]:buff个数
          OPT_DATA_MAX,                    //ID的上限
      };
      enum emModifyType
      {
          modify_type_set = 0,
          modify_data_inc,
          modify_type_dec,
      };
      static const unsigned int sc_max_data_cnt = 256;
      uint8 seatId;
      bool bspell;    // id是否spellid还是操作id
      uint16 id;
      uint8 datacnt;
      int data[sc_max_data_cnt];
      bool modify_data(unsigned int idx, MsgUpdateRoleData_Ex_Ntf::emModifyType modify_type, int value)
      {
          if (idx < sc_max_data_cnt && idx < datacnt)
          {
              switch (modify_type)
              {
              case modify_type_set:
                  data[idx] = value;
                  break;
              case modify_data_inc:
                  data[idx] += value;
                  break;
              case modify_type_dec:
                  data[idx] -= value;
                  break;
              default:
                  return false;
              }
              return true;
          }
          return false;
      }
      unsigned int push_data(int value)
      {
          if (datacnt < sc_max_data_cnt)
          {
              data[datacnt] = value;
              unsigned int ret = datacnt;
              ++datacnt;
              return ret;
          }
          return sc_max_data_cnt;
      }
      bool add_data(int v)
      {
          if (datacnt >= sc_max_data_cnt)
              return false;
          data[datacnt++] = v;
          return true;
      }
      unsigned int GetLen() { return sizeof(MsgUpdateRoleData_Ex_Ntf) - sizeof(data) + datacnt * sizeof(data[0]); }
      MsgUpdateRoleData_Ex_Ntf() : PacketBase(GS_C_UPDATE_ROLE_DATA_EX_NTF, sizeof(MsgUpdateRoleData_Ex_Ntf))
      {
          seatId = sc_invalid_seat_id;
          bspell = true;
          id = 0;
          datacnt = 0;
          for (unsigned int i=0;i<sc_max_data_cnt;i++)
              data[i] = 0;
      }
    };
    

桌子解散原因

  • 添加了 dismisstype_gs_get_roguelike_timeout、 dismisstype_gs_get_roguelike_err
    //非正常的解散桌子
    enum emdismiss_table_why
    {
      dismisstype_gscheck_alive  = 1,  //gs发现这个桌子是死的
      dismisstype_cs_synchro,          //cs上删除桌子完成通知
      dismisstype_matchtbl_userleave,  //gs自己解散比赛的桌子,因为有人离开了
      dismisstype_gs_OneCouncilOver,   //gs一局结束发现应该结束
      dismisstype_gs_notsetmaster,     //gs上设置不了房主
      dismisstype_gs_force_matchtbl,   //gs上一局比赛结束后强制解散
      dismisstype_gs_robotsitfail,     //gs上创建机器人失败
      dismisstype_gs_allrobots,        //gs上桌子里都是机器人
      dismisstype_gs_training_gameover,//练习桌子游戏结束解散
      dismisstype_ttranktbl_userleave, //gs自己解散天梯的桌子,因为有人离开了
      dismisstype_gs_force_ttranktbl,  //gs上一局天梯游戏结束后强制解散
      dismisstype_gs_ai_gameover,            //gs上ai场结束
      dismisstype_cmktbl_userleave,    //gs自己解散匹配模式的桌子,因为有人离开了
      dismisstype_gs_ready_failed,       //准备超时
      dismisstype_gs_get_roguelike_timeout,       // 获取爬塔数据超时
      dismisstype_gs_get_roguelike_err,       // 获取爬塔数据错误
    };
    

踢玩家下桌原因

-添加了 gs_get_roguelike_timeout、 gs_get_roguelike_err

//踢用户下线原因
struct TWhy_Kickuser
{
    enum em_kickuser_why
    {
        kickkind_begin_kind=1,

        //踢单个用户类型
        kickkind_gm,                                //gm

        kickkind_gateway_client_self_disconnect,    //客户端自己断开了连接
        kickkind_gateway_client_self_closeapp,        //客户端自己关闭了客户端

        kickkind_gateway_disconnect_assing_ls,        //gateway没有和这个ls服务器相连接
        kickkind_gateway_disconnect_assing_fs,        //gateway没有和这个fs服务器相连接
        kickkind_gateway_disconnect_assing_dbs,        //gateway没有和这个dbs服务器相连接
        kickkind_gateway_relogin,                    //gateway发现重复登录
        kickkind_gateway_clienthearttimeouts,        //client心跳超时
        kickkind_gateway_sendmsgtoclientfail,        //发送数据给client失败

        kickkind_ls_account_exist,                    //ls上发现账户存在 

        kickkind_cs_account_exist,                    //发现账户存在 
        kickkind_cs_account_timeout,                //cs上登录超时 
        kickkind_cs_reconnect_timeout,              //断线重连超时 
        kickkind_cs_reconnect_force_waitover,       //重连状态被迫结束,比如桌子解散等情况

        kickkind_gs_waitingrecon_notbl,             //cs通知这个用户进入断线重连状态,但gs上发现这个用户没有在游戏中
        kickkind_gs_client_modify_packet,           //客户端私自修改了数据包,导致出错

        //下面的是批量踢用户
        kickkind_cs_system_kickalluser,                //系统维护
        kickkind_cs_theserver_disconnectcs,         //这个服务器和cs的连接断开
        kickkind_cs_theserver_timeoutcs,            //这个服务器和cs发生了超时

        kickkind_cs_finishoffline_iscs,             //这个用户在cs上完成了下线 
        kickkind_gs_check_userdeath,                //gs逻辑检查这个用户是死的

        kickkind_cs_heartcheck_timeout,             //中心服务器上心跳超时  

        kickkind_gt_checkmsgpack_fail,              //gt检查消息报失败,踢用户
        kickkind_dbs_forbit_login,                  //dbs禁止这个用户登录
        kickkind_gt_sendmsgexceptin,                //发包频率异常
        kickkind_account_forbidden,                //账号被封

        kickkind_useroffline_end=200,

        gs_user_offline_selfreq_leavetable = kickkind_useroffline_end+1,
        gs_user_offline_req_sitdown_fail,            //请求坐到游戏桌子上失败
        gs_user_direct_del_user,                    //不要放入队列中,直接删除 
        gs_force_disband_gaming_why_all_disconnect, //gs强制解散桌子,因为所有人都断线了
        gs_user_offline_req_createtbl,                //请求创建桌子,发现已经存在这个用户
        gs_user_offline_master_kickme,              //在桌子上,我被房主踢下线了
        gs_user_createtbl_fail,                     //创建桌子失败
        gs_sys_tickuseroffline_fortimeout_notbegin, //你15秒钟内没有开始游戏
        gs_sys_check_table_notactive,               //发现这个桌子是死的
        gs_recivecs_force_userleavetbl,             //gs收到cs强制用户离开桌子
        gs_heartcheck_timeout,                      //gs上面的心跳超时
        gs_user_logicerror_mem,                     //gs上的程序逻辑错误,内存管理错误
        gs_match_createtbl_fail,        
        gs_match_begin_userleavetbl,                //当比赛开始的时候发现有人离开了桌子
        gs_match_inning_gameover,                   //一局比赛结束了
        cs_force_distbl,                            //cs要求强制解散桌子
        gs_logicerror_notsetmaster,                 //不能给桌子设置正确的房主
        gs_normal_inning_gameover,                  //一局结束后,逻辑需要
        gs_kickuser_robotsitfail,                   //机器人坐下失败  
        gs_all_robots,                              //桌子里都是机器人
        gs_training_tbl_gameover,                   //练习桌子游戏结束解散
        gs_ttrank_createtbl_fail,                   //创建天梯桌子失败
        gs_ttrank_begin_userleavetbl,               //当天梯开始的时候发现有人离开了桌子
        gs_ttrank_inning_gameover,                  //一局比赛结束了
        gs_ai_tbl_gameover,                            //AI场结束
        gs_cmktbl_createtbl_fail,                   //创建天梯桌子失败
        gs_cmktbl_begin_userleavetbl,               //当匹配模式开始的时候发现有人离开了桌子
        gs_arena_tuoguan_overtime,
        gs_match_ready_timeout,                     //匹配准备超时被踢
        gs_get_roguelike_timeout,             // 获取爬塔数据超时
        gs_get_roguelike_err,                // 获取爬塔数据错误
    };
};

爬塔积分排行榜

  • 爬塔积分排行榜只记录玩家当前赛季爬塔通关的最高积分

交互协议

  • 原有协议新增字段req_param1和req_param2,表示获取排行榜数据时需要的额外数据,默认用0填充;
  • 获取爬塔积分排行榜数据时,req_param1表示赛季号
enum emGameRankId
{
    kRankId_GameScore = 0,
    kRankId_OfficialPower = 1,
    kRankId_MobileHappy1v2 = 2,


    kRankId_Roguelike = 10,  // 爬塔积分榜
};

CLIENT_RKS_GAME_RANKING_REQ=45517
struct ClientRksGameRankingReq : PacketBase
{
    BYTE    rank_type;
    unsigned int rank_id;
    unsigned int count;

    unsigned int req_param1;
    unsigned int req_param2;
    ClientRksGameRankingReq() : PacketBase(CLIENT_RKS_GAME_RANKING_REQ, sizeof(ClientRksGameRankingReq))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRksGameRankingReq));
    }
};

CLIENT_RKS_GAME_RANKING_REP=45518
struct ClientRksGameRankingRep : public PacketBase
{
    ClientRksGameRankingRep() : PacketBase(CLIENT_RKS_GAME_RANKING_REP, sizeof(ClientRksGameRankingRep))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientRksGameRankingRep));
    }
    static const unsigned int sc_max_count = 200; // 分页请求,每次10个
    static const unsigned int sc_max_param_cnt = 3;
    struct TRankInfo
    {
        char            nickname[sc_max_nickname_len];
        int                score;
        BYTE            paramcnt;
        unsigned int    param[sc_max_param_cnt];

        friend bistream& operator>>(bistream& bis, TRankInfo& t)
        {
            bis >> t.nickname;
            bis >> t.score;
            bis >> t.paramcnt;
            for (BYTE i = 0; i < t.paramcnt; ++i)
                bis >> t.param[i];
            return bis;
        }

        friend bostream& operator<<(bostream& bos, const TRankInfo& t)
        {
            bos << t.nickname;
            bos << t.score;
            bos << t.paramcnt;
            for (BYTE i = 0; i < t.paramcnt; ++i)
                bos << t.param[i];
            return bos;
        }
    };
    friend bistream& operator>>(bistream& bis, ClientRksGameRankingRep& t)
    {
        //bis >> t.nickname;
        bis >> t.result;
        bis >> t.rank_type;
        bis >> t.rank_id;
        bis >> t.count;
        for (BYTE i = 0; i < t.count; ++i)
            bis >> t.rankinfo[i];
        return bis;
    }

    friend bostream& operator<<(bostream& bos, const ClientRksGameRankingRep& t)
    {
        //bos << t.nickname;
        bos << t.result;
        bos << t.rank_type;
        bos << t.rank_id;
        bos << t.count;
        for (BYTE i = 0; i < t.count; ++i)
            bos << t.rankinfo[i];
        return bos;
    }
    BYTE    result;
    BYTE    rank_type;
    unsigned int rank_id;
    unsigned int count; //返回的结果数量,如果<sc_page_count说明到最后一页了
    TRankInfo rankinfo[sc_max_count];

    unsigned int rep_param1;
    unsigned int rep_param2;
};

//自己的排名
CLIENT_RKS_MY_GAME_RANKING_REQ=45519
struct ClientMyGameRankingReq : public PacketBase
{
    ClientMyGameRankingReq() : PacketBase(CLIENT_RKS_MY_GAME_RANKING_REQ, sizeof(ClientMyGameRankingReq))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientMyGameRankingReq));
    }
    char acc[sc_max_account_len];
    BYTE    rank_type;
    unsigned int rank_id;

    unsigned int req_param1;
    unsigned int req_param2;
};

CLIENT_RKS_MY_GAME_RANKING_REP=45520
struct ClientMyGameRankingRep : public PacketBase
{
    ClientMyGameRankingRep() : PacketBase(CLIENT_RKS_MY_GAME_RANKING_REP, sizeof(ClientMyGameRankingRep))
    {
        PRO_ZERO_MEMORY(this, sizeof(ClientMyGameRankingRep));
    }
    BYTE    result;
    BYTE    rank_type;
    unsigned int rank_id;
    unsigned int curpos;
    int score;

    unsigned int rep_param1;
    unsigned int rep_param2;
};