From 8e11ec5f9fa1c130f6f680e932b64311a7fc5df0 Mon Sep 17 00:00:00 2001 From: UnKownOwO <80520429@qq.com> Date: Sat, 7 Jan 2023 00:38:06 +0800 Subject: [PATCH] =?UTF-8?q?GCG=E6=B5=81=E7=A8=8B=E8=87=B3=E6=8A=95?= =?UTF-8?q?=E6=8E=B7=E9=AA=B0=E5=AD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gdconf/game_data_config.go | 2 + gdconf/game_data_config/csv/GCGCharData.csv | 75 ++ gdconf/gcg_char_data.go | 66 ++ gs/game/gcg_ai.go | 43 ++ gs/game/gcg_manager.go | 766 ++++++++++++-------- gs/game/route_manager.go | 1 + gs/game/tick_manager.go | 4 + gs/game/user_gcg.go | 88 ++- protocol/cmd/cmd_id_proto_obj_map.go | 13 +- 9 files changed, 741 insertions(+), 317 deletions(-) create mode 100644 gdconf/game_data_config/csv/GCGCharData.csv create mode 100644 gdconf/gcg_char_data.go create mode 100644 gs/game/gcg_ai.go diff --git a/gdconf/game_data_config.go b/gdconf/game_data_config.go index 3fb03e4e..45f5dc9d 100644 --- a/gdconf/game_data_config.go +++ b/gdconf/game_data_config.go @@ -21,6 +21,7 @@ type GameDataConfig struct { AvatarSkillDataMap map[int32]*AvatarSkillData // 角色技能 AvatarSkillDepotDataMap map[int32]*AvatarSkillDepotData // 角色技能库 DropGroupDataMap map[int32]*DropGroupData // 掉落组 + GCGCharDataMap map[int32]*GCGCharData // 角色卡牌 } func InitGameDataConfig() { @@ -62,6 +63,7 @@ func (g *GameDataConfig) load() { g.loadAvatarSkillData() // 角色技能 g.loadAvatarSkillDepotData() // 角色技能库 g.loadDropGroupData() // 掉落组 + g.loadGCGCharData() // 角色卡牌 } func (g *GameDataConfig) readCsvFileData(fileName string) []byte { diff --git a/gdconf/game_data_config/csv/GCGCharData.csv b/gdconf/game_data_config/csv/GCGCharData.csv new file mode 100644 index 00000000..c1ae0b34 --- /dev/null +++ b/gdconf/game_data_config/csv/GCGCharData.csv @@ -0,0 +1,75 @@ +CharId,,TagId1,TagId2,TagId3,TagId4,TagId5,,,,,SkillListStr,,HPBase,MaxElemVal,, +int32,,int32,int32,int32,int32,int32,,,,,string,,int32,int32,, +ID,卡牌类型,[卡牌标签列表]1,[卡牌标签列表]2,[卡牌标签列表]3,[卡牌标签列表]4,[卡牌标签列表]5,[卡牌变量]1类型,[卡牌变量]1值,[卡牌变量]2类型,[卡牌变量]2值,卡牌技能列表,可进构筑,角色生命值,角色充能上限,最大行动意图数,死亡后移出游戏 +1001,1,303,503,502,201,,,,,,80,,50,1,1, +1002,1,301,503,502,204,,,,,,80,,50,1,1,1 +1003,1,303,503,502,201,,,,,,80#1130211,,50,1,1, +1101,1,301,204,402,,,16,0,,,11011# 11012# 11013# 11014,1,10,2,2, +1102,1,301,204,401,,,,,,,11021# 11022# 11023,1,10,3,2, +1103,1,301,201,401,,,,,,,11031# 11032# 11033,1,10,2,2, +1104,1,301,203,402,,,,,,,11041# 11042# 11043,1,10,3,2, +1105,1,301,201,403,,,,,,,11051# 11052# 11053# 11054,1,10,3,2, +1201,1,302,202,401,,,,,,,12011# 12012# 12013,1,10,3,2, +1202,1,302,201,402,,,,,,,12021# 12022# 12023,1,10,2,2, +1203,1,302,202,401,,,,,,,12031# 12032# 12033# 12034,1,10,3,2, +1301,1,303,203,401,,,,,,,13011# 13012# 13013,1,10,3,2, +1302,1,303,205,402,,,,,,,13021# 13022# 13023,1,10,2,2, +1303,1,303,201,401,,,,,,,13031# 13032# 13033,1,10,2,2, +1305,1,303,204,403,,,,,,,13051# 13052# 13053,1,10,2,2, +1401,1,304,204,401,,,,,,,14011# 14012# 14013,1,10,3,2, +1402,1,304,203,401,,,,,,,14021# 14022# 14023,1,10,3,2, +1403,1,304,201,402,,,,,,,14031# 14032# 14033,1,10,3,2, +1404,1,304,205,404,,,,,,,14041# 14042# 14043# 14044,1,10,2,2, +1501,1,307,202,401,,,,,,,15011# 15012# 15013,1,10,2,2, +1502,1,307,201,401,,,,,,,15021# 15022# 15023,1,10,3,2, +1601,1,305,202,402,,,,,,,16011# 16012# 16013,1,10,3,2, +1602,1,305,203,401,,,,,,,16021# 16022# 16023,1,10,2,2, +1701,1,306,204,404,,,,,,,17011# 17012# 17013,1,10,2,2, +2201,1,302,200,503,,,,,,,22011# 22012# 22013# 22014,1,10,3,2, +2202,1,302,200,501,,,,,,,22021# 22022# 22023,1,10,2,2, +2301,1,303,200,501,,,,,,,23011# 23012# 23013# 23014,1,10,2,2, +2501,1,307,200,503,,,,,,,25011# 25012# 25013# 25014,1,10,3,2, +2601,1,305,200,503,502,,,,,,26011# 26012# 26013# 26014,1,8,2,2, +2701,1,306,200,503,,,,,,,27011# 27012# 27013# 27014,1,10,2,2, +3001,1,300,503,502,,,,,,,30011# 30012# 30013,,4,2,1,1 +3002,1,300,503,502,,,,,,,30021# 30022# 30023,,3,2,1,1 +3003,1,300,504,,,,,,,,30031# 30032# 30033# 30034# 30035# 30036,,6,2,1,1 +3101,1,301,503,502,,,,,,,31011# 31012# 31013# 31014# 31015,,8,2,2,1 +3102,1,301,503,502,,,,,,,31021# 31022# 31023,,3,2,1,1 +3103,1,301,503,,,,,,,,31031# 31032# 31033# 31034# 31035,,4,2,2,1 +3201,1,302,503,,,,,,,,32011# 32012# 32013# 32014# 32015,,4,3,2,1 +3202,1,302,503,502,,,,,,,32021# 32022# 32023,,4,2,2,1 +3203,1,302,501,,,,,,,,32031# 32032# 32033# 32034# 32035,,10,3,2,1 +3204,1,302,503,,,,,,,,32041# 32042# 32043,,4,2,1,1 +3301,1,303,503,,,,,,,,33011# 33012# 33013# 33014# 33015,,4,3,2,1 +3302,1,303,503,502,,,,,,,33021# 33022# 33023# 33024,,8,2,2,1 +3303,1,303,504,,,,,,,,33031# 33032# 33033# 33034# 33035# 33036# 33037,,10,2,2,1 +3304,1,303,503,502,,,,,,,33041# 33042# 33043,,4,2,1,1 +3305,1,303,504,,,,,,,,33051# 33052# 30033# 30034# 30035# 30036,,6,2,1,1 +3401,1,304,504,,,,,,,,34011# 34012# 34013# 34014# 34015# 34016# 34017,,10,2,2,1 +3402,1,304,503,502,,,,,,,34021# 34022# 34023,,3,2,1,1 +3403,1,304,503,502,,,,,,,34031# 34032# 34033# 34034,,8,2,2,1 +3405,1,304,504,,,,,,,,34051# 34052# 30033# 30034# 30035# 30036,,6,2,1,1 +3406,1,304,503,,,,,,,,34061# 34062# 34063,,4,2,1,1 +3501,1,307,503,502,,,,,,,35011# 35012# 35013,,4,2,2,1 +3502,1,307,501,,,,,,,,35021# 35022# 35023# 35024# 35025,,10,2,2,1 +3701,1,306,503,,,,,,,,37011# 37012# 37013# 37014,,6,2,2,1 +3703,1,306,503,502,,,,,,,37031# 37032# 37033,,4,2,1,1 +4601,1,305,200,503,502,,,,,,26011# 26012 # 26013,,12,2,2,1 +4602,1,305,200,503,502,,,,,,26011# 26012 # 26013# 26014,,16,2,3,1 +4603,1,305,200,503,502,,,,,,26011# 26012 # 26013# 26014,,24,2,3,1 +4604,1,305,200,503,502,,,,,,26011# 26012 # 26013# 26014,,8,2,2,1 +4201,1,302,200,503,,,,,,,22011# 22012 # 22013# 22014# 32043,,30,3,4,1 +4202,1,302,200,503,,,,,,,22011# 22012 # 22013# 22014# 32043,,45,3,4,1 +4203,1,302,200,501,,,,,,,22021# 22022 # 22023,,10,2,3,1 +4204,1,302,200,501,,,,,,,22021# 22022 # 22023,,10,2,2,1 +4301,1,303,200,501,,,,,,,23011# 23012 # 23013# 23014,,10,2,3,1 +4302,1,303,200,501,,,,,,,23011# 23012 # 23013# 23014,,10,2,2,1 +4501,1,307,200,503,,,,,,,25011# 25012 # 25013# 25014,,20,3,2,1 +4502,1,307,200,503,,,,,,,25011# 25012 # 25013# 25014,,28,3,2,1 +4701,1,306,200,503,,,,,,,27011# 27012 # 27013# 27014,,36,2,4,1 +4702,1,306,200,503,,,,,,,27011# 27012 # 27013# 27014,,48,2,4,1 +4703,1,306,200,503,,,,,,,27011# 27012 # 27013# 27014,,10,2,2,1 +4704,1,306,200,503,,,,,,,27011# 27012 # 27013# 27014,,30,2,3,1 +5201,1,302,503,,,,,,,,32011# 32012# 32013# 32014# 32015# 32016,,4,3,2,1 +5301,1,303,503,,,,,,,,33011# 33012# 33013# 33014# 33015# 33016,,4,3,2,1 diff --git a/gdconf/gcg_char_data.go b/gdconf/gcg_char_data.go new file mode 100644 index 00000000..b8f2184f --- /dev/null +++ b/gdconf/gcg_char_data.go @@ -0,0 +1,66 @@ +package gdconf + +import ( + "fmt" + "strconv" + "strings" + + "hk4e/pkg/logger" + + "github.com/jszwec/csvutil" +) + +// 角色卡牌配置表 + +type GCGCharData struct { + CharId int32 `csv:"CharId"` // ID + TagId1 int32 `csv:"TagId1,omitempty"` // 卡牌标签列表1 + TagId2 int32 `csv:"TagId2,omitempty"` // 卡牌标签列表2 + TagId3 int32 `csv:"TagId3,omitempty"` // 卡牌标签列表3 + TagId4 int32 `csv:"TagId4,omitempty"` // 卡牌标签列表4 + TagId5 int32 `csv:"TagId5,omitempty"` // 卡牌标签列表5 + SkillListStr string `csv:"SkillListStr,omitempty"` // 卡牌技能列表文本 + HPBase int32 `csv:"HPBase,omitempty"` // 角色生命值 + MaxElemVal int32 `csv:"MaxElemVal,omitempty"` // 角色充能上限 + + TagList []uint32 // 卡牌标签列表 + SkillList []uint32 // 卡牌技能列表 +} + +func (g *GameDataConfig) loadGCGCharData() { + g.GCGCharDataMap = make(map[int32]*GCGCharData) + data := g.readCsvFileData("GCGCharData.csv") + var gcgCharDataList []*GCGCharData + err := csvutil.Unmarshal(data, &gcgCharDataList) + if err != nil { + info := fmt.Sprintf("parse file error: %v", err) + panic(info) + } + for _, gcgCharData := range gcgCharDataList { + // 将TagId整合进TagList + gcgCharData.TagList = make([]uint32, 0, 5) + tempTagList := make([]int32, 0, 5) + tempTagList = append(tempTagList, gcgCharData.TagId1, gcgCharData.TagId2, gcgCharData.TagId3, gcgCharData.TagId4, gcgCharData.TagId5) + for _, tagId := range tempTagList { + // 跳过为0的tag + if tagId == 0 { + continue + } + gcgCharData.TagList = append(gcgCharData.TagList, uint32(tagId)) + } + // 技能列表读取转换 + tempSkillList := strings.Split(strings.ReplaceAll(gcgCharData.SkillListStr, " ", ""), "#") + gcgCharData.SkillList = make([]uint32, 0, len(tempSkillList)) + for _, s := range tempSkillList { + skillId, err := strconv.Atoi(s) + if err != nil { + logger.Error("skill id to i err, %v", err) + return + } + gcgCharData.SkillList = append(gcgCharData.SkillList, uint32(skillId)) + } + // list -> map + g.GCGCharDataMap[gcgCharData.CharId] = gcgCharData + } + logger.Info("GCGCharData count: %v", len(g.GCGCharDataMap)) +} diff --git a/gs/game/gcg_ai.go b/gs/game/gcg_ai.go new file mode 100644 index 00000000..032538f6 --- /dev/null +++ b/gs/game/gcg_ai.go @@ -0,0 +1,43 @@ +package game + +import ( + "hk4e/pkg/logger" + "hk4e/protocol/proto" + "time" +) + +type GCGAi struct { + game *GCGGame // 所在的游戏 + controllerId uint32 // 操控者Id +} + +// ReceiveGCGMessagePackNotify 接收GCG消息包通知 +func (g *GCGAi) ReceiveGCGMessagePackNotify(notify *proto.GCGMessagePackNotify) { + // 获取玩家的操控者对象 + gameController := g.game.controllerMap[g.controllerId] + if gameController == nil { + logger.Error("ai 角色 nil") + return + } + + for _, pack := range notify.MsgPackList { + for _, message := range pack.MsgList { + switch message.Message.(type) { + case *proto.GCGMessage_PhaseChange: + // 阶段改变 + msg := message.GetPhaseChange() + switch msg.AfterPhase { + case proto.GCGPhaseType_GCG_PHASE_TYPE_ON_STAGE: + logger.Error("请选择你的英雄 hhh") + go func() { + time.Sleep(1000 * 3) + // 默认选第一张牌 + cardInfo := gameController.cardList[0] + // 操控者选择角色牌 + g.game.ControllerSelectChar(gameController, cardInfo, []uint32{}) + }() + } + } + } + } +} diff --git a/gs/game/gcg_manager.go b/gs/game/gcg_manager.go index 0c98b6ef..807635cb 100644 --- a/gs/game/gcg_manager.go +++ b/gs/game/gcg_manager.go @@ -1,17 +1,23 @@ package game import ( + "hk4e/gdconf" "hk4e/gs/model" "hk4e/pkg/logger" + "hk4e/pkg/random" + "hk4e/protocol/cmd" "hk4e/protocol/proto" + "math/rand" + "time" ) // ControllerType 操控者类型 type ControllerType uint8 const ( - ControllerType_Player ControllerType = iota // 玩家 - ControllerType_AI // AI + ControllerType_None ControllerType = iota + ControllerType_Player // 玩家 + ControllerType_AI // AI ) // GCGCardInfo 游戏对局内卡牌 @@ -36,12 +42,24 @@ const ( // GCGController 操控者 type GCGController struct { - controllerId uint32 // 操控者Id - cardMap map[uint32]*GCGCardInfo // 卡牌列表 - loadState ControllerLoadState // 加载状态 - controllerType ControllerType // 操控者的类型 - player *model.Player - ai uint32 // 暂时不写 + controllerId uint32 // 操控者Id + cardList []*GCGCardInfo // 卡牌列表 + loadState ControllerLoadState // 加载状态 + allow uint32 // 是否允许操控 0 -> 不允许 1 -> 允许 + selectedCharCard *GCGCardInfo // 选择的角色卡牌 + controllerType ControllerType // 操控者的类型 + player *model.Player // 玩家对象 + ai *GCGAi // AI对象 +} + +// GetCardByGuid 通过卡牌的Guid获取卡牌 +func (g *GCGController) GetCardByGuid(cardGuid uint32) *GCGCardInfo { + for _, info := range g.cardList { + if info.guid == cardGuid { + return info + } + } + return nil } // GCGManager 七圣召唤管理器 @@ -63,14 +81,14 @@ func (g *GCGManager) CreateGame(gameId uint32, playerList []*model.Player) *GCGG guid: g.gameGuidCounter, gameId: gameId, roundInfo: &GCGRoundInfo{ - roundNum: 1, // 默认以第一回合开始 - allowControllerMap: make(map[uint32]uint32, 0), - firstController: 1, // 1号操控者为先手 + roundNum: 1, // 默认以第一回合开始 + firstController: 1, // 1号操控者为先手 + diceSideMap: make(map[uint32][]proto.GCGDiceSideType, 2), }, - controllerMap: make(map[uint32]*GCGController, 0), - controllerMsgPackMap: make(map[uint32][]*proto.GCGMessagePack), - historyCardList: make([]*proto.GCGCard, 0, 0), - historyMsgPackList: make([]*proto.GCGMessagePack, 0, 0), + controllerMap: make(map[uint32]*GCGController, 2), + msgPackList: make([]*proto.GCGMessagePack, 0, 10), + historyMsgPackList: make([]*proto.GCGMessagePack, 0, 50), + historyCardList: make([]*GCGCardInfo, 0, 100), } // 初始化游戏 game.InitGame(playerList) @@ -79,20 +97,351 @@ func (g *GCGManager) CreateGame(gameId uint32, playerList []*model.Player) *GCGG return game } -// GCGMsgPhaseChange GCG消息阶段改变 -func (g *GCGManager) GCGMsgPhaseChange(game *GCGGame, afterPhase proto.GCGPhaseType) *proto.GCGMessage { - gcgMsgPhaseChange := &proto.GCGMsgPhaseChange{ - BeforePhase: game.roundInfo.phaseType, - AfterPhase: afterPhase, - AllowControllerMap: make([]*proto.Uint32Pair, 0, len(game.controllerMap)), +type GCGGameState uint8 + +const ( + GCGGameState_None GCGGameState = iota + GCGGameState_Waiting // 等待玩家加载 + GCGGameState_Running // 游戏运行中 + GCGGameState_Stoped // 游戏已结束 +) + +// 阶段对应的处理函数 +var phaseFuncMap = map[proto.GCGPhaseType]func(game *GCGGame){ + proto.GCGPhaseType_GCG_PHASE_TYPE_START: PhaseStartReady, + proto.GCGPhaseType_GCG_PHASE_TYPE_ON_STAGE: PhaseSelectChar, + proto.GCGPhaseType_GCG_PHASE_TYPE_DICE: PhaseRollDice, + proto.GCGPhaseType_GCG_PHASE_TYPE_PRE_MAIN: PhasePreMain, +} + +// PhaseStartReady 阶段开局准备 +func PhaseStartReady(game *GCGGame) { + // 客户端更新操控者 + game.AddMsgPack(0, proto.GCGActionType_GCG_ACTION_TYPE_NONE, game.GCGMsgUpdateController()) + // 分配先手 + game.AddMsgPack(0, proto.GCGActionType_GCG_ACTION_TYPE_PHASE_EXIT, game.GCGMsgClientPerform(proto.GCGClientPerformType_GCG_CLIENT_PERFORM_TYPE_FIRST_HAND, []uint32{game.roundInfo.firstController})) + // 游戏绘制卡牌阶段 应该 + game.GameChangePhase(proto.GCGPhaseType_GCG_PHASE_TYPE_DRAW) + // 游戏选择角色卡牌阶段 + game.GameChangePhase(proto.GCGPhaseType_GCG_PHASE_TYPE_ON_STAGE) +} + +// PhaseSelectChar 阶段选择角色卡牌 +func PhaseSelectChar(game *GCGGame) { + // 该阶段确保每位玩家都选择了角色 + for _, controller := range game.controllerMap { + if controller.selectedCharCard == nil { + // 如果有没选择角色卡牌的则不执行后面 + return + } } - // 开始阶段所有玩家允许操作 - if afterPhase == proto.GCGPhaseType_GCG_PHASE_TYPE_START || afterPhase == proto.GCGPhaseType_GCG_PHASE_TYPE_ON_STAGE || afterPhase == proto.GCGPhaseType_GCG_PHASE_TYPE_MAIN { - for controllerId := range game.controllerMap { - gcgMsgPhaseChange.AllowControllerMap = append(gcgMsgPhaseChange.AllowControllerMap, &proto.Uint32Pair{ - Key: controllerId, - Value: 1, - }) + // 回合信息 + game.AddMsgPack(0, proto.GCGActionType_GCG_ACTION_TYPE_SEND_MESSAGE, game.GCGMsgDuelDataChange()) + // 游戏投掷骰子阶段 + game.GameChangePhase(proto.GCGPhaseType_GCG_PHASE_TYPE_DICE) +} + +// PhaseRollDice 阶段投掷骰子 +func PhaseRollDice(game *GCGGame) { + // 给每位玩家投掷骰子 + for _, controller := range game.controllerMap { + diceSideList := make([]proto.GCGDiceSideType, 0, 8) + rand.Seed(time.Now().UnixNano()) // 随机数种子 + // 玩家需要8个骰子 + for i := 0; i < 8; i++ { + diceSide := proto.GCGDiceSideType(random.GetRandomInt32(1, 8)) + diceSideList = append(diceSideList, diceSide) + } + // 存储该回合玩家的骰子 + game.roundInfo.diceSideMap[controller.controllerId] = diceSideList + game.AddMsgPack(controller.controllerId, proto.GCGActionType_GCG_ACTION_TYPE_ROLL, game.GCGMsgDiceRoll(controller.controllerId, uint32(len(diceSideList)), diceSideList)) + } + game.GameChangePhase(proto.GCGPhaseType_GCG_PHASE_TYPE_REROLL) + // g.controllerMap[1].allow = 1 + // g.controllerMap[2].allow = 0 + // msgPackInfo := g.CreateMsgPackInfo() + // msgPackInfo.AddMsgPack(0, proto.GCGActionType_GCG_ACTION_TYPE_NONE, g.GCGMsgPVEIntention(&proto.GCGMsgPVEIntention{CardGuid: g.controllerMap[2].cardList[0].guid, SkillIdList: []uint32{g.controllerMap[2].cardList[0].skillIdList[1]}}, &proto.GCGMsgPVEIntention{CardGuid: g.controllerMap[2].cardList[1].guid, SkillIdList: []uint32{g.controllerMap[2].cardList[1].skillIdList[0]}})) + // g.BroadcastMsgPackInfo(msgPackInfo, false) + // msgPackInfo1 := g.CreateMsgPackInfo() + // msgPackInfo1.AddMsgPack(0, proto.GCGActionType_GCG_ACTION_TYPE_NONE, g.GCGMsgUpdateController()) + // msgPackInfo1.AddMsgPack(0, proto.GCGActionType_GCG_ACTION_TYPE_SEND_MESSAGE, g.GCGMsgPhaseContinue()) + // g.BroadcastMsgPackInfo(msgPackInfo1, false) +} + +// PhasePreMain 阶段战斗开始 +func PhasePreMain(game *GCGGame) { + game.AddMsgPack(0, proto.GCGActionType_GCG_ACTION_TYPE_TRIGGER_SKILL, game.GCGMsgUseSkill(195, 33024), game.GCGMsgUseSkillEnd(195, 33024)) +} + +// GCGRoundInfo 游戏对局回合信息 +type GCGRoundInfo struct { + roundNum uint32 // 游戏当前回合数 + phaseType proto.GCGPhaseType // 现在所处的阶段类型 + firstController uint32 // 当前回合先手的操控者 + diceSideMap map[uint32][]proto.GCGDiceSideType // 操控者骰子列表 uint32 -> controllerId +} + +// GCGGame 游戏对局 +type GCGGame struct { + guid uint32 // 唯一Id + gameId uint32 // 游戏Id + gameState GCGGameState // 游戏运行状态 + gameTick uint32 // 游戏tick + serverSeqCounter uint32 // 请求序列生成计数器 + controllerIdCounter uint32 // 操控者Id生成器 + cardGuidCounter uint32 // 卡牌guid生成计数器 + roundInfo *GCGRoundInfo // 游戏回合信息 + controllerMap map[uint32]*GCGController // 操控者列表 uint32 -> controllerId + msgPackList []*proto.GCGMessagePack // 消息包待发送区 + historyMsgPackList []*proto.GCGMessagePack // 历史消息包列表 + historyCardList []*GCGCardInfo // 历史卡牌列表 +} + +// AddPlayer GCG游戏添加玩家 +func (g *GCGGame) AddPlayer(player *model.Player) { + // 创建操控者 + g.controllerIdCounter++ + controller := &GCGController{ + controllerId: g.controllerIdCounter, + cardList: make([]*GCGCardInfo, 0, 50), + loadState: ControllerLoadState_None, + controllerType: ControllerType_Player, + player: player, + } + // 生成卡牌信息 + g.GiveCharCard(controller, 1301) + g.GiveCharCard(controller, 1103) + // 记录操控者 + g.controllerMap[g.controllerIdCounter] = controller + player.GCGCurGameGuid = g.guid +} + +// AddAI GCG游戏添加AI +func (g *GCGGame) AddAI() { + // 创建操控者 + g.controllerIdCounter++ + controller := &GCGController{ + controllerId: g.controllerIdCounter, + cardList: make([]*GCGCardInfo, 0, 50), + loadState: ControllerLoadState_InitFinish, + controllerType: ControllerType_AI, + ai: &GCGAi{ + game: g, + controllerId: g.controllerIdCounter, + }, + } + // 生成卡牌信息 + g.GiveCharCard(controller, 3001) + g.GiveCharCard(controller, 3302) + // 记录操控者 + g.controllerMap[g.controllerIdCounter] = controller +} + +// GameChangePhase 游戏更改阶段 +func (g *GCGGame) GameChangePhase(phase proto.GCGPhaseType) { + beforePhase := g.roundInfo.phaseType + // 修改游戏的阶段 + g.roundInfo.phaseType = phase + switch phase { + case proto.GCGPhaseType_GCG_PHASE_TYPE_ON_STAGE, proto.GCGPhaseType_GCG_PHASE_TYPE_DICE: + // 该阶段允许所有玩家行动 + for _, controller := range g.controllerMap { + controller.allow = 1 + } + case proto.GCGPhaseType_GCG_PHASE_TYPE_PRE_MAIN: + // 该阶段不允许所有玩家行动 + for _, controller := range g.controllerMap { + controller.allow = 0 + } + } + g.AddMsgPack(0, proto.GCGActionType_GCG_ACTION_TYPE_NEXT_PHASE, g.GCGMsgPhaseChange(beforePhase, phase)) +} + +// GiveCharCard 给予操控者角色卡牌 +func (g *GCGGame) GiveCharCard(controller *GCGController, charId uint32) { + // 读取角色卡牌配置表 + gcgCharConfig, ok := gdconf.CONF.GCGCharDataMap[int32(charId)] + if !ok { + logger.Error("gcg char config error, charId: %v", charId) + return + } + // 生成卡牌信息 + g.cardGuidCounter++ + controller.cardList = append(controller.cardList, &GCGCardInfo{ + cardId: charId, + guid: g.cardGuidCounter, + faceType: 0, // 1为金卡 + tagList: gcgCharConfig.TagList, + tokenMap: map[uint32]uint32{ + 1: uint32(gcgCharConfig.HPBase), // 血量 + 2: uint32(gcgCharConfig.HPBase), // 最大血量(不确定) + 4: 0, // 充能 + 5: uint32(gcgCharConfig.MaxElemVal), // 充能条 + }, + skillIdList: gcgCharConfig.SkillList, + skillLimitList: []uint32{}, + isShow: true, + }) +} + +// ControllerSelectChar 操控者选择角色卡牌 +func (g *GCGGame) ControllerSelectChar(controller *GCGController, cardInfo *GCGCardInfo, costDiceIndexList []uint32) { + // 判断选择角色卡牌消耗的点数是否正确 + if controller.selectedCharCard != nil && len(costDiceIndexList) == 0 { + // 首次选择角色牌不消耗点数 + return + } + // TODO 消耗骰子点数 + // 设置角色卡牌 + controller.selectedCharCard = cardInfo + // 设置操控者禁止操作 + controller.allow = 0 + // 广播选择的角色卡牌消息包 + g.AddMsgPack(0, proto.GCGActionType_GCG_ACTION_TYPE_NONE, g.GCGMsgUpdateController()) + g.AddMsgPack(controller.controllerId, proto.GCGActionType_GCG_ACTION_TYPE_SELECT_ONSTAGE, g.GCGMsgSelectOnStage(controller.controllerId, cardInfo.guid, proto.GCGReason_GCG_REASON_DEFAULT)) +} + +// onTick 游戏的Tick +func (g *GCGGame) onTick() { + // 判断游戏是否运行中 + if g.gameState != GCGGameState_Running { + return + } + // 每10s触发 + if g.gameTick%10 == 0 { + // GCG游戏心跳包 + for _, controller := range g.controllerMap { + // 跳过AI + if controller.player == nil { + continue + } + gcgHeartBeatNotify := &proto.GCGHeartBeatNotify{ + ServerSeq: g.serverSeqCounter, + } + GAME_MANAGER.SendMsg(cmd.GCGHeartBeatNotify, controller.player.PlayerID, controller.player.ClientSeq, gcgHeartBeatNotify) + } + } + // 仅在游戏处于运行状态时执行阶段处理 + if g.gameState == GCGGameState_Running { + phaseFunc, ok := phaseFuncMap[g.roundInfo.phaseType] + // 确保该阶段有进行处理的函数 + if ok { + phaseFunc(g) // 进行该阶段的处理 + // 发送阶段处理后的消息包 + g.SendAllMsgPack(false) + } + } + g.gameTick++ +} + +// InitGame 初始化GCG游戏 +func (g *GCGGame) InitGame(playerList []*model.Player) { + // 初始化玩家 + for _, player := range playerList { + g.AddPlayer(player) + } + // 添加AI + g.AddAI() + + // // 先手允许操作 + // controller, ok := g.controllerMap[g.roundInfo.firstController] + // if !ok { + // logger.Error("controller is nil, controllerId: %v", g.roundInfo.firstController) + // return + // } + // controller.allow = 1 + + // TODO 验证玩家人数是否符合 + // 预开始游戏 + g.GameChangePhase(proto.GCGPhaseType_GCG_PHASE_TYPE_START) + g.AddMsgPack(0, proto.GCGActionType_GCG_ACTION_TYPE_NONE, g.GCGMsgUpdateController()) + g.AddMsgPack(0, proto.GCGActionType_GCG_ACTION_TYPE_SEND_MESSAGE, g.GCGMsgPhaseContinue()) + g.SendAllMsgPack(true) + + // 游戏状态更改为等待玩家加载 + g.gameState = GCGGameState_Waiting +} + +// StartGame 开始GCG游戏 +func (g *GCGGame) StartGame() { + // 游戏开始设置所有玩家不允许操作 + // for _, c := range g.controllerMap { + // c.allow = 0 + // } + + // 游戏状态更改为游戏运行中 + g.gameState = GCGGameState_Running +} + +// CheckAllInitFinish 检查所有玩家是否加载完成 +func (g *GCGGame) CheckAllInitFinish() { + // 判断游戏是否已开始 + if g.gameState == GCGGameState_Running { + logger.Error("gcg game is running") + return + } + // 检查所有玩家是否加载完成 + for _, controller := range g.controllerMap { + if controller.loadState != ControllerLoadState_InitFinish { + return + } + } + // TODO 可能会玩家中途退了 超时结束游戏 + // 正式开始游戏 + g.StartGame() +} + +// AddMsgPack 添加GCG消息包至待发送区 +func (g *GCGGame) AddMsgPack(controllerId uint32, actionType proto.GCGActionType, msgList ...*proto.GCGMessage) { + pack := &proto.GCGMessagePack{ + ActionType: actionType, + MsgList: make([]*proto.GCGMessage, 0, len(msgList)), + ControllerId: controllerId, + } + // 将每个GCG消息添加进消息包中 + for _, message := range msgList { + pack.MsgList = append(pack.MsgList, message) + } + // 将消息包添加进待发送区 + g.msgPackList = append(g.msgPackList, pack) +} + +// SendAllMsgPack 发送所有待发送区的消息包 +func (g *GCGGame) SendAllMsgPack(recordOnly bool) { + // 不发送空的消息包 + if len(g.msgPackList) == 0 { + return + } + // 是否仅记录历史消息包 + if !recordOnly { + g.serverSeqCounter++ + for _, controller := range g.controllerMap { + GAME_MANAGER.SendGCGMessagePackNotify(controller, g.serverSeqCounter, g.msgPackList) + } + } + // 记录发送的历史消息包 + for _, pack := range g.msgPackList { + g.historyMsgPackList = append(g.historyMsgPackList, pack) + } + // 清空待发送区消息包 + g.msgPackList = make([]*proto.GCGMessagePack, 0, 10) +} + +// GCGMsgPhaseChange GCG消息阶段改变 +func (g *GCGGame) GCGMsgPhaseChange(beforePhase proto.GCGPhaseType, afterPhase proto.GCGPhaseType) *proto.GCGMessage { + gcgMsgPhaseChange := &proto.GCGMsgPhaseChange{ + BeforePhase: beforePhase, + AfterPhase: afterPhase, + AllowControllerMap: make([]*proto.Uint32Pair, 0, len(g.controllerMap)), + } + if gcgMsgPhaseChange.AfterPhase != proto.GCGPhaseType_GCG_PHASE_TYPE_DRAW && gcgMsgPhaseChange.AfterPhase != proto.GCGPhaseType_GCG_PHASE_TYPE_PRE_MAIN { + // 操控者的是否允许操作 + for _, controller := range g.controllerMap { + pair := &proto.Uint32Pair{ + Key: controller.controllerId, + Value: controller.allow, + } + gcgMsgPhaseChange.AllowControllerMap = append(gcgMsgPhaseChange.AllowControllerMap, pair) } } gcgMessage := &proto.GCGMessage{ @@ -100,13 +449,11 @@ func (g *GCGManager) GCGMsgPhaseChange(game *GCGGame, afterPhase proto.GCGPhaseT PhaseChange: gcgMsgPhaseChange, }, } - // 修改游戏的阶段状态 - game.roundInfo.phaseType = afterPhase return gcgMessage } // GCGMsgPhaseContinue GCG消息阶段跳过 -func (g *GCGManager) GCGMsgPhaseContinue() *proto.GCGMessage { +func (g *GCGGame) GCGMsgPhaseContinue() *proto.GCGMessage { gcgMsgPhaseContinue := &proto.GCGMsgPhaseContinue{} gcgMessage := &proto.GCGMessage{ Message: &proto.GCGMessage_PhaseContinue{ @@ -117,16 +464,17 @@ func (g *GCGManager) GCGMsgPhaseContinue() *proto.GCGMessage { } // GCGMsgUpdateController GCG消息更新操控者 -func (g *GCGManager) GCGMsgUpdateController(game *GCGGame) *proto.GCGMessage { +func (g *GCGGame) GCGMsgUpdateController() *proto.GCGMessage { gcgMsgUpdateController := &proto.GCGMsgUpdateController{ - AllowControllerMap: make([]*proto.Uint32Pair, 0, len(game.controllerMap)), + AllowControllerMap: make([]*proto.Uint32Pair, 0, len(g.controllerMap)), } - // 操控者的允许次数 - for controllerId, _ := range game.roundInfo.allowControllerMap { - gcgMsgUpdateController.AllowControllerMap = append(gcgMsgUpdateController.AllowControllerMap, &proto.Uint32Pair{ - Key: controllerId, - Value: 0, - }) + // 操控者的是否允许操作 + for _, controller := range g.controllerMap { + pair := &proto.Uint32Pair{ + Key: controller.controllerId, + Value: controller.allow, + } + gcgMsgUpdateController.AllowControllerMap = append(gcgMsgUpdateController.AllowControllerMap, pair) } gcgMessage := &proto.GCGMessage{ Message: &proto.GCGMessage_UpdateController{ @@ -137,7 +485,7 @@ func (g *GCGManager) GCGMsgUpdateController(game *GCGGame) *proto.GCGMessage { } // GCGMsgClientPerform GCG消息客户端执行 -func (g *GCGManager) GCGMsgClientPerform(performType proto.GCGClientPerformType, paramList []uint32) *proto.GCGMessage { +func (g *GCGGame) GCGMsgClientPerform(performType proto.GCGClientPerformType, paramList []uint32) *proto.GCGMessage { gcgMsgClientPerform := &proto.GCGMsgClientPerform{ ParamList: paramList, PerformType: performType, @@ -150,278 +498,92 @@ func (g *GCGManager) GCGMsgClientPerform(performType proto.GCGClientPerformType, return gcgMessage } -// GCGRoundInfo 游戏对局回合信息 -type GCGRoundInfo struct { - roundNum uint32 // 游戏当前回合数 - phaseType proto.GCGPhaseType // 现在所处的阶段类型 - allowControllerMap map[uint32]uint32 // 回合内操控者允许的次数 - firstController uint32 // 当前回合先手的操控者 -} - -// GCGGame 游戏对局 -type GCGGame struct { - guid uint32 // 唯一Id - gameId uint32 // 游戏Id - serverSeqCounter uint32 // 请求序列生成计数器 - controllerIdCounter uint32 // 操控者Id生成器 - cardGuidCounter uint32 // 卡牌guid生成计数器 - roundInfo *GCGRoundInfo // 游戏回合信息 - controllerMap map[uint32]*GCGController // 操控者列表 uint32 -> controllerId - controllerMsgPackMap map[uint32][]*proto.GCGMessagePack // 操控者消息包待发送区 0代表全局 - // TODO 游戏重连 - historyCardList []*proto.GCGCard // 历史发送的卡牌 - historyMsgPackList []*proto.GCGMessagePack // 历史发送的消息包 -} - -// AddPlayer GCG游戏添加玩家 -func (g *GCGGame) AddPlayer(player *model.Player) { - // 创建操控者 - g.controllerIdCounter++ - controller := &GCGController{ - controllerId: g.controllerIdCounter, - cardMap: make(map[uint32]*GCGCardInfo, 0), - loadState: ControllerLoadState_None, - controllerType: ControllerType_Player, - player: player, - } - // 生成卡牌信息 - g.cardGuidCounter++ - controller.cardMap[1301] = &GCGCardInfo{ - cardId: 1301, - guid: g.cardGuidCounter, - faceType: 0, - tagList: []uint32{203, 303, 401}, - tokenMap: map[uint32]uint32{ - 1: 10, - 2: 10, - 4: 0, - 5: 3, - }, - skillIdList: []uint32{ - 13011, - 13012, - 13013, - }, - skillLimitList: []uint32{}, - isShow: true, - } - g.cardGuidCounter++ - controller.cardMap[1103] = &GCGCardInfo{ - cardId: 1103, - guid: g.cardGuidCounter, - faceType: 0, - tagList: []uint32{201, 301, 401}, - tokenMap: map[uint32]uint32{ - 1: 10, // 血量 - 2: 10, // 最大血量(不确定) - 4: 0, // 充能 - 5: 2, // 充能条 - }, - skillIdList: []uint32{ - 11031, - 11032, - 11033, - }, - skillLimitList: []uint32{}, - isShow: true, - } - g.cardGuidCounter++ - controller.cardMap[3001] = &GCGCardInfo{ - cardId: 3001, - guid: g.cardGuidCounter, - faceType: 0, - tagList: []uint32{200, 300, 502, 503}, - tokenMap: map[uint32]uint32{ - 1: 4, - 2: 4, - 4: 0, - 5: 2, - }, - skillIdList: []uint32{ - 30011, - 30012, - 30013, - }, - skillLimitList: []uint32{}, - isShow: true, - } - // g.cardGuidCounter++ - // controller.cardMap[1301011] = &GCGCardInfo{ - // cardId: 1301011, - // guid: g.cardGuidCounter, - // faceType: 0, - // skillIdList: []uint32{ - // 13010111, - // }, - // skillLimitList: []uint32{}, - // isShow: true, - // } - // 记录操控者 - g.controllerMap[g.controllerIdCounter] = controller - player.GCGCurGameGuid = g.guid -} - -// AddAI GCG游戏添加AI -func (g *GCGGame) AddAI() { - // 创建操控者 - g.controllerIdCounter++ - controller := &GCGController{ - controllerId: g.controllerIdCounter, - cardMap: make(map[uint32]*GCGCardInfo, 0), - loadState: ControllerLoadState_InitFinish, - controllerType: ControllerType_AI, - ai: 233, - } - // 生成卡牌信息 - g.cardGuidCounter++ - controller.cardMap[3001] = &GCGCardInfo{ - cardId: 3001, - guid: g.cardGuidCounter, - faceType: 0, - tagList: []uint32{200, 300, 502, 503}, - tokenMap: map[uint32]uint32{ - 1: 4, - 2: 4, - 4: 0, - 5: 2, - }, - skillIdList: []uint32{ - 30011, - 30012, - 30013, - }, - skillLimitList: []uint32{}, - isShow: true, - } - g.cardGuidCounter++ - controller.cardMap[3302] = &GCGCardInfo{ - cardId: 3302, - guid: g.cardGuidCounter, - faceType: 0, - tagList: []uint32{200, 303, 502, 503}, - tokenMap: map[uint32]uint32{ - 1: 8, - 2: 8, - 4: 0, - 5: 2, - }, - skillIdList: []uint32{ - 33021, - 33022, - 33023, - 33024, - }, - skillLimitList: []uint32{}, - isShow: true, - } - // 记录操控者 - g.controllerMap[g.controllerIdCounter] = controller -} - -// InitGame 初始化GCG游戏 -func (g *GCGGame) InitGame(playerList []*model.Player) { - // 初始化玩家 - for _, player := range playerList { - g.AddPlayer(player) - } - // 添加AI - g.AddAI() - - // 初始化每个操控者的次数 - for controllerId := range g.controllerMap { - g.roundInfo.allowControllerMap[controllerId] = 0 - } - // 先手者操控数为1 - g.roundInfo.allowControllerMap[g.roundInfo.firstController] = 1 - - // TODO 验证玩家人数是否符合 - // 预开始游戏 - g.AddMessagePack(0, proto.GCGActionType_GCG_ACTION_TYPE_NONE, GCG_MANAGER.GCGMsgPhaseChange(g, proto.GCGPhaseType_GCG_PHASE_TYPE_START), GCG_MANAGER.GCGMsgUpdateController(g)) - g.AddMessagePack(0, proto.GCGActionType_GCG_ACTION_TYPE_SEND_MESSAGE, GCG_MANAGER.GCGMsgPhaseContinue()) - g.SendMessagePack(0) - // 预开始游戏后 ServerSeq 会跟官服不同 这里重置一下 - g.serverSeqCounter = 0 - logger.Error("gcg init") -} - -// StartGame 开始GCG游戏 -func (g *GCGGame) StartGame() { - // 开始游戏消息包 - g.AddMessagePack(0, proto.GCGActionType_GCG_ACTION_TYPE_NONE, GCG_MANAGER.GCGMsgUpdateController(g)) - g.AddMessagePack(0, proto.GCGActionType_GCG_ACTION_TYPE_PHASE_EXIT, GCG_MANAGER.GCGMsgClientPerform(proto.GCGClientPerformType_GCG_CLIENT_PERFORM_TYPE_FIRST_HAND, []uint32{g.roundInfo.firstController})) - g.AddMessagePack(0, proto.GCGActionType_GCG_ACTION_TYPE_NEXT_PHASE, GCG_MANAGER.GCGMsgPhaseChange(g, proto.GCGPhaseType_GCG_PHASE_TYPE_DRAW)) - g.AddMessagePack(0, proto.GCGActionType_GCG_ACTION_TYPE_NEXT_PHASE, GCG_MANAGER.GCGMsgPhaseChange(g, proto.GCGPhaseType_GCG_PHASE_TYPE_ON_STAGE)) - g.SendMessagePack(0) - logger.Error("gcg start") -} - -// CheckAllInitFinish 检查所有玩家是否加载完成 -func (g *GCGGame) CheckAllInitFinish() { - // 检查所有玩家是否加载完成 - for _, controller := range g.controllerMap { - if controller.loadState != ControllerLoadState_InitFinish { - return - } - } - // TODO 可能会玩家中途退了 超时结束游戏 - // 正式开始游戏 - g.StartGame() -} - -// AddMessagePack 添加操控者的待发送区GCG消息 -func (g *GCGGame) AddMessagePack(controllerId uint32, actionType proto.GCGActionType, msgList ...*proto.GCGMessage) { - _, ok := g.controllerMsgPackMap[controllerId] - if !ok { - g.controllerMsgPackMap[controllerId] = make([]*proto.GCGMessagePack, 0, len(msgList)*5) - } - pack := &proto.GCGMessagePack{ - ActionType: actionType, - MsgList: make([]*proto.GCGMessage, 0, len(msgList)), +// GCGMsgSelectOnStage GCG消息切换角色卡牌 +func (g *GCGGame) GCGMsgSelectOnStage(controllerId uint32, cardGuid uint32, reason proto.GCGReason) *proto.GCGMessage { + gcgMsgClientPerform := &proto.GCGMsgSelectOnStage{ + Reason: reason, ControllerId: controllerId, + CardGuid: cardGuid, } - // 将每个GCG消息添加进消息包中 - for _, message := range msgList { - pack.MsgList = append(pack.MsgList, message) + gcgMessage := &proto.GCGMessage{ + Message: &proto.GCGMessage_SelectOnStage{ + SelectOnStage: gcgMsgClientPerform, + }, } - // 将消息包添加进待发送区 - g.controllerMsgPackMap[controllerId] = append(g.controllerMsgPackMap[controllerId], pack) + return gcgMessage } -// SendMessagePack 发送操控者的待发送区GCG消息 -func (g *GCGGame) SendMessagePack(controllerId uint32) { - msgPackList, ok := g.controllerMsgPackMap[controllerId] - if !ok { - logger.Error("msg pack list error, controllerId: %v", controllerId) - return +// GCGMsgPVEIntention GCG消息PVE意向 +func (g *GCGGame) GCGMsgPVEIntention(pveIntentionList ...*proto.GCGMsgPVEIntention) *proto.GCGMessage { + gcgMsgPVEIntention := &proto.GCGMsgPVEIntentionInfo{ + IntentionMap: make(map[uint32]*proto.GCGMsgPVEIntention), } - // 0代表广播给全体玩家 - if controllerId == 0 { - g.serverSeqCounter++ - for _, controller := range g.controllerMap { - GAME_MANAGER.SendGCGMessagePackNotify(controller, g.serverSeqCounter, msgPackList) - } - } else { - // 获取指定的操控者 - controller, ok := g.controllerMap[controllerId] - if !ok { - logger.Error("controller is nil, controllerId: %v", controllerId) - return - } - g.serverSeqCounter++ - GAME_MANAGER.SendGCGMessagePackNotify(controller, g.serverSeqCounter, msgPackList) + for _, intention := range pveIntentionList { + gcgMsgPVEIntention.IntentionMap[intention.CardGuid] = intention } - // 记录发送的历史消息包 - for _, pack := range msgPackList { - g.historyMsgPackList = append(g.historyMsgPackList, pack) + gcgMessage := &proto.GCGMessage{ + Message: &proto.GCGMessage_PveIntentionInfo{ + PveIntentionInfo: gcgMsgPVEIntention, + }, } - // 清空待发送区的数据 - g.controllerMsgPackMap[controllerId] = make([]*proto.GCGMessagePack, 0, len(msgPackList)) + return gcgMessage } -// // CreateGameCardInfo 生成操控者卡牌信息 -// func (g *GCGManager) CreateGameCardInfo(controller *GCGController, gcgDeck *model.GCGDeck) *GCGCardInfo { -// -// } +// GCGMsgDuelDataChange GCG消息切换回合 +func (g *GCGGame) GCGMsgDuelDataChange() *proto.GCGMessage { + gcgMsgDuelDataChange := &proto.GCGMsgDuelDataChange{ + Round: g.roundInfo.roundNum, + } + gcgMessage := &proto.GCGMessage{ + Message: &proto.GCGMessage_DuelDataChange{ + DuelDataChange: gcgMsgDuelDataChange, + }, + } + return gcgMessage +} + +// GCGMsgDiceRoll GCG消息摇骰子 +func (g *GCGGame) GCGMsgDiceRoll(controllerId uint32, diceNum uint32, diceSideList []proto.GCGDiceSideType) *proto.GCGMessage { + gcgMsgDiceRoll := &proto.GCGMsgDiceRoll{ + ControllerId: controllerId, + DiceNum: diceNum, + DiceSideList: diceSideList, + } + gcgMessage := &proto.GCGMessage{ + Message: &proto.GCGMessage_DiceRoll{ + DiceRoll: gcgMsgDiceRoll, + }, + } + return gcgMessage +} + +// GCGMsgUseSkill GCG消息使用技能 +func (g *GCGGame) GCGMsgUseSkill(cardGuid uint32, skillId uint32) *proto.GCGMessage { + gcgMsgMsgUseSkill := &proto.GCGMsgUseSkill{ + SkillId: skillId, + CardGuid: cardGuid, + } + gcgMessage := &proto.GCGMessage{ + Message: &proto.GCGMessage_UseSkill{ + UseSkill: gcgMsgMsgUseSkill, + }, + } + return gcgMessage +} + +// GCGMsgUseSkillEnd GCG消息使用技能结束 +func (g *GCGGame) GCGMsgUseSkillEnd(cardGuid uint32, skillId uint32) *proto.GCGMessage { + gcgMsgMsgUseSkillEnd := &proto.GCGMsgUseSkillEnd{ + SkillId: skillId, + CardGuid: cardGuid, + } + gcgMessage := &proto.GCGMessage{ + Message: &proto.GCGMessage_UseSkillEnd{ + UseSkillEnd: gcgMsgMsgUseSkillEnd, + }, + } + return gcgMessage +} // GetControllerByUserId 通过玩家Id获取GCGController对象 func (g *GCGGame) GetControllerByUserId(userId uint32) *GCGController { diff --git a/gs/game/route_manager.go b/gs/game/route_manager.go index 3bc26053..86681812 100644 --- a/gs/game/route_manager.go +++ b/gs/game/route_manager.go @@ -126,6 +126,7 @@ func (r *RouteManager) initRoute() { r.registerRouter(cmd.GetOnlinePlayerInfoReq, GAME_MANAGER.GetOnlinePlayerInfoReq) r.registerRouter(cmd.GCGAskDuelReq, GAME_MANAGER.GCGAskDuelReq) r.registerRouter(cmd.GCGInitFinishReq, GAME_MANAGER.GCGInitFinishReq) + r.registerRouter(cmd.GCGOperationReq, GAME_MANAGER.GCGOperationReq) } func (r *RouteManager) RouteHandle(netMsg *mq.NetMsg) { diff --git a/gs/game/tick_manager.go b/gs/game/tick_manager.go index b5fb4ae0..83848ecb 100644 --- a/gs/game/tick_manager.go +++ b/gs/game/tick_manager.go @@ -363,6 +363,10 @@ func (t *TickManager) onTickSecond(now int64) { } } } + // GCG游戏Tick + for _, game := range GCG_MANAGER.gameMap { + game.onTick() + } } func (t *TickManager) onTick200MilliSecond(now int64) { diff --git a/gs/game/user_gcg.go b/gs/game/user_gcg.go index ca3cfedb..bf3e8fc0 100644 --- a/gs/game/user_gcg.go +++ b/gs/game/user_gcg.go @@ -3,6 +3,7 @@ package game import ( "hk4e/common/constant" "hk4e/gs/model" + "hk4e/pkg/logger" "hk4e/protocol/cmd" "hk4e/protocol/proto" @@ -158,7 +159,7 @@ func (g *GameManager) GCGAskDuelReq(player *model.Player, payloadMsg pb.Message) // 阶段数据 Phase: &proto.GCGPhase{ PhaseType: game.roundInfo.phaseType, - AllowControllerMap: game.roundInfo.allowControllerMap, + AllowControllerMap: make(map[uint32]uint32), }, }, } @@ -178,7 +179,7 @@ func (g *GameManager) GCGAskDuelReq(player *model.Player, payloadMsg pb.Message) playerField := &proto.GCGPlayerField{ Unk3300_IKJMGAHCFPM: 0, // 卡牌图片 - ModifyZoneMap: make(map[uint32]*proto.GCGZone, len(controller.cardMap)), + ModifyZoneMap: make(map[uint32]*proto.GCGZone, len(controller.cardList)), Unk3300_GGHKFFADEAL: 0, Unk3300_AOPJIOHMPOF: &proto.GCGZone{ CardList: []uint32{}, @@ -196,7 +197,7 @@ func (g *GameManager) GCGAskDuelReq(player *model.Player, payloadMsg pb.Message) ControllerId: controller.controllerId, // 卡牌位置 Unk3300_INDJNJJJNKL: &proto.GCGZone{ - CardList: make([]uint32, 0, len(controller.cardMap)), + CardList: make([]uint32, 0, len(controller.cardList)), }, Unk3300_EFNAEFBECHD: &proto.GCGZone{ CardList: []uint32{}, @@ -208,7 +209,7 @@ func (g *GameManager) GCGAskDuelReq(player *model.Player, payloadMsg pb.Message) Unk3300_GLNIFLOKBPM: 0, } // 卡牌信息 - for _, info := range controller.cardMap { + for _, info := range controller.cardList { gcgCard := &proto.GCGCard{ TagList: info.tagList, Guid: info.guid, @@ -239,6 +240,8 @@ func (g *GameManager) GCGAskDuelReq(player *model.Player, payloadMsg pb.Message) // Field playerField.ModifyZoneMap[info.guid] = &proto.GCGZone{CardList: []uint32{}} playerField.Unk3300_INDJNJJJNKL.CardList = append(playerField.Unk3300_INDJNJJJNKL.CardList, info.guid) + // Phase + gcgAskDuelRsp.Duel.Phase.AllowControllerMap[controller.controllerId] = controller.allow } // 添加完所有卡牌的位置之类的信息 添加这个牌盒 gcgAskDuelRsp.Duel.FieldList = append(gcgAskDuelRsp.Duel.FieldList, playerField) @@ -276,12 +279,68 @@ func (g *GameManager) GCGInitFinishReq(player *model.Player, payloadMsg pb.Messa game.CheckAllInitFinish() } -// SendGCGMessagePackNotify 发送GCG消息包通知 -func (g *GameManager) SendGCGMessagePackNotify(controller *GCGController, serverSeq uint32, msgPackList []*proto.GCGMessagePack) { - // 确保为玩家 - if controller.player == nil { +// GCGOperationReq GCG游戏客户端操作请求 +func (g *GameManager) GCGOperationReq(player *model.Player, payloadMsg pb.Message) { + req := payloadMsg.(*proto.GCGOperationReq) + + // 获取玩家所在的游戏 + game, ok := GCG_MANAGER.gameMap[player.GCGCurGameGuid] + if !ok { + g.CommonRetError(cmd.GCGOperationRsp, player, &proto.GCGOperationRsp{}, proto.Retcode_RET_GCG_GAME_NOT_RUNNING) return } + // 获取玩家的操控者对象 + gameController := game.GetControllerByUserId(player.PlayerID) + if gameController == nil { + g.CommonRetError(cmd.GCGOperationRsp, player, &proto.GCGOperationRsp{}, proto.Retcode_RET_GCG_NOT_IN_GCG_DUNGEON) + return + } + + switch req.Op.Op.(type) { + case *proto.GCGOperation_OpSelectOnStage: + // 选择角色卡牌 + op := req.Op.GetOpSelectOnStage() + // 操作者是否拥有该卡牌 + cardInfo := gameController.GetCardByGuid(op.CardGuid) + if cardInfo == nil { + GAME_MANAGER.CommonRetError(cmd.GCGOperationRsp, player, &proto.GCGOperationRsp{}, proto.Retcode_RET_GCG_SELECT_HAND_CARD_GUID_ERROR) + return + } + // 操控者选择角色牌 + game.ControllerSelectChar(gameController, cardInfo, op.CostDiceIndexList) + case *proto.GCGOperation_OpReroll: + // 确认骰子不重投 + op := req.Op.GetOpReroll() + diceSideList, ok := game.roundInfo.diceSideMap[gameController.controllerId] + if !ok { + g.CommonRetError(cmd.GCGOperationRsp, player, &proto.GCGOperationRsp{}, proto.Retcode_RET_GCG_DICE_INDEX_INVALID) + return + } + // 判断骰子索引是否有效 + for _, diceIndex := range op.DiceIndexList { + if diceIndex > uint32(len(diceSideList)) { + g.CommonRetError(cmd.GCGOperationRsp, player, &proto.GCGOperationRsp{}, proto.Retcode_RET_GCG_DICE_INDEX_INVALID) + return + } + } + // 客户端更新操控者 + game.AddMsgPack(0, proto.GCGActionType_GCG_ACTION_TYPE_NONE, game.GCGMsgUpdateController()) + // 游戏行动阶段 + game.GameChangePhase(proto.GCGPhaseType_GCG_PHASE_TYPE_PRE_MAIN) + default: + logger.Error("gcg op is not handle, op: %T", req.Op.Op) + g.CommonRetError(cmd.GCGOperationRsp, player, &proto.GCGOperationRsp{}, proto.Retcode_RET_GCG_OPERATION_PARAM_ERROR) + return + } + + // PacketGCGOperationRsp + gcgOperationRsp := &proto.GCGOperationRsp{ + OpSeq: req.OpSeq, + } + GAME_MANAGER.SendMsg(cmd.GCGOperationRsp, player.PlayerID, player.ClientSeq, gcgOperationRsp) +} + +func (g *GameManager) SendGCGMessagePackNotify(controller *GCGController, serverSeq uint32, msgPackList []*proto.GCGMessagePack) { // 确保加载完成 if controller.loadState != ControllerLoadState_InitFinish { return @@ -291,7 +350,16 @@ func (g *GameManager) SendGCGMessagePackNotify(controller *GCGController, server ServerSeq: serverSeq, MsgPackList: msgPackList, } - GAME_MANAGER.SendMsg(cmd.GCGMessagePackNotify, controller.player.PlayerID, controller.player.ClientSeq, gcgMessagePackNotify) + // 根据操控者的类型发送消息包 + switch controller.controllerType { + case ControllerType_Player: + GAME_MANAGER.SendMsg(cmd.GCGMessagePackNotify, controller.player.PlayerID, controller.player.ClientSeq, gcgMessagePackNotify) + case ControllerType_AI: + controller.ai.ReceiveGCGMessagePackNotify(gcgMessagePackNotify) + default: + logger.Error("controller type error, %v", controller.controllerType) + return + } } // PacketGCGGameBriefDataNotify GCG游戏简要数据通知 @@ -309,7 +377,7 @@ func (g *GameManager) PacketGCGGameBriefDataNotify(player *model.Player, busines gcgPlayerBriefData := &proto.GCGPlayerBriefData{ ControllerId: controller.controllerId, ProfilePicture: new(proto.ProfilePicture), - CardIdList: make([]uint32, 0, len(controller.cardMap)), + CardIdList: make([]uint32, 0, len(controller.cardList)), } // 玩家信息 if controller.player != nil { diff --git a/protocol/cmd/cmd_id_proto_obj_map.go b/protocol/cmd/cmd_id_proto_obj_map.go index f2884d30..8807cc80 100644 --- a/protocol/cmd/cmd_id_proto_obj_map.go +++ b/protocol/cmd/cmd_id_proto_obj_map.go @@ -260,11 +260,14 @@ func (c *CmdProtoMap) registerAllMessage() { c.registerMessage(GCGTCTavernInfoNotify, &proto.GCGTCTavernInfoNotify{}) // GCG酒馆信息通知 c.registerMessage(GCGTavernNpcInfoNotify, &proto.GCGTavernNpcInfoNotify{}) // GCG酒馆NPC信息通知 c.registerMessage(GCGGameBriefDataNotify, &proto.GCGGameBriefDataNotify{}) // GCG游戏简要数据通知 - c.registerMessage(GCGAskDuelReq, &proto.GCGAskDuelReq{}) // GCG决斗请求 - c.registerMessage(GCGAskDuelRsp, &proto.GCGAskDuelRsp{}) // GCG决斗响应 - c.registerMessage(GCGInitFinishReq, &proto.GCGInitFinishReq{}) // GCG初始化完成请求 - c.registerMessage(GCGInitFinishRsp, &proto.GCGInitFinishRsp{}) // GCG初始化完成响应 - c.registerMessage(GCGMessagePackNotify, &proto.GCGMessagePackNotify{}) // GCG消息包通知 + c.registerMessage(GCGAskDuelReq, &proto.GCGAskDuelReq{}) // GCG游戏对局信息请求 + c.registerMessage(GCGAskDuelRsp, &proto.GCGAskDuelRsp{}) // GCG游戏对局信息响应 + c.registerMessage(GCGInitFinishReq, &proto.GCGInitFinishReq{}) // GCG游戏初始化完成请求 + c.registerMessage(GCGInitFinishRsp, &proto.GCGInitFinishRsp{}) // GCG游戏初始化完成响应 + c.registerMessage(GCGMessagePackNotify, &proto.GCGMessagePackNotify{}) // GCG游戏消息包通知 + c.registerMessage(GCGHeartBeatNotify, &proto.GCGHeartBeatNotify{}) // GCG游戏心跳包通知 + c.registerMessage(GCGOperationReq, &proto.GCGOperationReq{}) // GCG游戏客户端操作请求 + c.registerMessage(GCGOperationRsp, &proto.GCGOperationRsp{}) // GCG游戏客户端操作响应 // // TODO 客户端开始GCG游戏 // c.registerMessage(GCGStartChallengeByCheckRewardReq, &proto.GCGStartChallengeByCheckRewardReq{}) // GCG开始挑战来自检测奖励请求