| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430 |
- package battle
- import (
- capp "f1-game/internal/cherry_app"
- "f1-game/internal/code"
- "f1-game/internal/component/redis"
- "f1-game/internal/constant"
- "f1-game/internal/data"
- "f1-game/internal/enum"
- nameActor "f1-game/internal/name/actor"
- nameRemote "f1-game/internal/name/remote"
- "f1-game/internal/pb"
- "f1-game/internal/types"
- "fmt"
- "time"
- cfacade "github.com/cherry-game/cherry/facade"
- ctime "github.com/cherry-game/cherry/extend/time"
- clog "github.com/cherry-game/cherry/logger"
- cnats "github.com/cherry-game/cherry/net/nats"
- )
- const (
- BATTLE_VERIFY_SUBJECT = "BattleValidation" // 战斗验证主题
- BATTLE_RECORD_ID_ERR_PREFIX = "error" // 报错验证的录像ID前缀
- )
- // 战斗验证
- func Validation(recordID string, battleParam *pb.BattleParam) *pb.BattleResult {
- start := time.Now()
- ret := validationRequest(recordID, battleParam)
- cost := time.Since(start)
- // 打印耗时
- if cost > 100*time.Millisecond {
- clog.Warnf("[BattleValidation] cost time = %v", cost)
- }
- return ret
- }
- func validationRequest(recordID string, battleParam *pb.BattleParam) *pb.BattleResult {
- reqData, err := capp.Serializer().Marshal(battleParam)
- if err != nil {
- clog.Warnf("[BattleValidation] Marshal battleParam error. err = %v", err)
- return nil
- }
- conn := cnats.GetConnect()
- if conn == nil {
- clog.Errorf("[BattleValidation] nats connect is nil")
- return nil
- }
- // TODO 待后面战斗逻辑稳定后,修改为 conn.RequestSync()
- replyData, err := conn.Request(BATTLE_VERIFY_SUBJECT, reqData)
- if err != nil {
- clog.Warnf("[BattleValidation] Request replyData error. err = %v", err)
- return nil
- }
- reply := &pb.BattleReplay{}
- err = capp.Serializer().Unmarshal(replyData, reply)
- if err != nil {
- clog.Warnf("[BattleValidation] Unmarshal replyData error. err = %v", err)
- return nil
- }
- if code.IsFail(reply.ErrorCode) {
- clog.Warnf("[BattleValidation] battle fail. errorCode = %v, errorText = %v", reply.ErrorCode, reply.ErrorText)
- // 错误就以时间格式为recordID
- recordID = fmt.Sprintf("%s_%d_%s", BATTLE_RECORD_ID_ERR_PREFIX, reply.ErrorCode, ctime.Now().ToShortDateTimeFormat())
- sendBattleRecord(recordID, reqData, true)
- return nil
- }
- // 如果设置生成配置数据,则给录像赋值配置数据
- if battleParam.GenerateConfigData && len(reply.Result.ConfigData) > 0 {
- battleParam.ConfigData = reply.Result.ConfigData
- if reqData, err = capp.Serializer().Marshal(battleParam); err != nil {
- clog.Warnf("[BattleValidation] Marshal GenerateConfigData battleParam error. err = %v", err)
- return nil
- }
- }
- // 保存成功的请求数据
- sendBattleRecord(recordID, reqData, false)
- return reply.Result
- }
- // 发送给record节点存储战斗录像
- func sendBattleRecord(recordID string, reqData []byte, isFail bool) {
- var (
- req = &pb.RecordData{
- RecordID: recordID,
- Data: reqData,
- IsFail: isFail,
- }
- )
- // 随机一个record节点
- randRecord, found := capp.Discovery().Random(constant.NodeRecord)
- if !found {
- clog.Warnf("[sendBattleRecord] random record node error. node = %s", constant.NodeRecord)
- return
- }
- target := cfacade.NewPath(randRecord.GetNodeID(), nameActor.Record)
- capp.Call(target, nameRemote.Map_BattleRecord, req)
- }
- // 获取最大血量
- func GetMaxHealth(attrs *pb.Attrs) int64 {
- var maxHp int64
- for _, attr := range attrs.List {
- if attr.Key == enum.Attr_MaxHP {
- maxHp = attr.Value
- break
- }
- }
- return maxHp
- }
- // 设置剩余血量
- func SetHealth(attrs *pb.Attrs, hpPct int32) {
- maxHP := GetMaxHealth(attrs)
- //根据最大血量和当前剩余血量万分比计算剩余血量
- hp := maxHP * int64(hpPct) / constant.RatioBase
- for _, attr := range attrs.List {
- if attr.Key == enum.Attr_HP {
- clog.Debugf("SetHealth: maxHP=%d, hpPct=%d, hp=[%d ->%d]", maxHP, hpPct, attr.Value, hp)
- attr.Value = max(0, hp)
- break
- }
- }
- }
- // 检查所有战斗小队小兵数量
- func CheckBattleSquadTroopsNum(battleCampParam *pb.BattleCampParam) bool {
- // 一个小队都没有
- if len(battleCampParam.Squads) == 0 {
- return false
- }
- // 怪物的不需要检查,可以不带小兵
- if battleCampParam.PlayerID == 0 {
- return true
- }
- for _, squad := range battleCampParam.Squads {
- if squad.TroopsNum > 0 {
- return true
- }
- }
- return false
- }
- func NewTeamBattleDamage(teamID int32, objectID int64) *pb.TeamBattleDamage {
- return &pb.TeamBattleDamage{
- TeamID: teamID,
- ObjectID: objectID,
- Members: map[int32]*pb.TeamMemberDamage{},
- }
- }
- // 获取队伍战后数据
- func GetBattleSquadResult(camp int32, id int64, br *pb.BattleResult) *pb.BattleSquadResult {
- for _, result := range br.CampResult {
- if result.Camp == camp {
- for _, squad := range result.SquadResults {
- if squad.FighterResult.Id == id {
- return squad
- }
- }
- }
- }
- return nil
- }
- // 根据阵营获取战斗后的阵容数据
- func GetBattleCampResult(br *pb.BattleResult, camp int32) *pb.BattleCampResult {
- for _, result := range br.CampResult {
- if result.Camp == camp {
- return result
- }
- }
- return nil
- }
- // 更新战斗对象剩余兵力
- func UpdateBattleParam(battleParam *pb.BattleCampParam, battleResult *pb.BattleResult) int32 {
- var (
- totalTroopLoss = int32(0) // 总战损兵力
- newSquads []*pb.BattleSquadParam // 新的小队参数列表
- )
- for _, squad := range battleParam.Squads {
- squadResult := GetBattleSquadResult(battleParam.Camp, squad.Fighter.Id, battleResult)
- totalTroopLoss += squad.TroopsNum - squadResult.SoldierNum
- squad.TroopsNum = squadResult.SoldierNum
- // 设置血量
- // SetHealth(squad.Attrs, squadResult.FighterResult.Hp)
- // 过滤阵容剩余兵力为0的小队
- if squad.TroopsNum > 0 {
- newSquads = append(newSquads, squad)
- }
- }
- // 更新小队数据
- battleParam.Squads = newSquads
- return totalTroopLoss
- }
- // 统一处理英雄属性和小兵属性拆分
- func handleHeroAndSoldierAttr(heroID int32, attrs types.Attrs) (*pb.Attrs, *pb.Attrs) {
- var (
- heroConfig, _ = data.Hero.GetByID(heroID)
- job = enum.Job(heroConfig.Job)
- heroAttrs = types.Attrs{}
- troopsAttrs = types.Attrs{}
- )
- for key, attrVal := range attrs {
- if enum.SquadKeys[key] {
- switch key {
- case enum.Attr_SquadAttackBonus, enum.Attr_SquadHealthBonus:
- troopsAttrs.Add(key, attrVal)
- case enum.Attr_InfantryAllAttributeBonus:
- if job == enum.Job_Infantry {
- troopsAttrs.Add(key, attrVal)
- }
- case enum.Attr_CavalryAllAttributeBonus:
- if job == enum.Job_Cavalry {
- troopsAttrs.Add(key, attrVal)
- }
- case enum.Attr_ArcherAllAttributeBonus:
- if job == enum.Job_Archer {
- troopsAttrs.Add(key, attrVal)
- }
- }
- } else {
- heroAttrs.Add(key, attrVal)
- }
- }
- // TODO 设置英雄当前血量为最大血量
- heroAttrs.Set(enum.Attr_HP, heroAttrs.Get(enum.Attr_MaxHP))
- return heroAttrs.ToProto(), troopsAttrs.ToProto()
- }
- // 获取战斗请求的所有玩家战斗参数,包括所有英雄所在部队的位置
- func GetServerBattleCampParams(battleReq *pb.BattleReq) (map[int64]*pb.ServerBattleCampParam, bool) {
- battleTeamDataList := battleReq.Attackers
- // pvp 防守方战斗数据
- if enum.IsTilePVPBattle(enum.TileBattleType(battleReq.BattleType)) {
- battleTeamDataList = append(battleTeamDataList, battleReq.Defender)
- }
- if len(battleTeamDataList) == 0 {
- return nil, false
- }
- // 从redis一次性获取玩家战斗数据
- teamMap, heroMap := redis.Game.GetPlayerBattleData(battleTeamDataList...)
- if len(teamMap) == 0 || len(heroMap) == 0 {
- return nil, false
- }
- // 将单个玩家的战斗数据封装成一个ServerBattleCampParam
- buildCampParam := func(battleTeamData *pb.BattleTeamData) *pb.ServerBattleCampParam {
- team := teamMap[battleTeamData.PlayerID]
- if team == nil {
- return nil
- }
- param := &pb.ServerBattleCampParam{
- Param: &pb.BattleCampParam{
- Camp: enum.BattleCampType_Left,
- PlayerID: battleTeamData.PlayerID,
- },
- HeroTeamIndex: &pb.I64I32Map{
- Value: map[int64]int32{},
- },
- }
- // 判断是否是防守方
- if battleTeamData.PlayerID == battleReq.Defender.PlayerID {
- param.Param.Camp = enum.BattleCampType_Right
- }
- for _, heroID := range battleTeamData.TeamHeros {
- if fighter, found := heroMap[heroID]; found {
- teamMemeber := team.Members[heroID]
- attrs := types.NewAttrs()
- // 英雄的基础属性
- for _, attr := range fighter.Attrs.List {
- attrs.Add(attr.Key, attr.Value)
- }
- // 羁绊属性
- for _, attr := range teamMemeber.FetterAttrs.List {
- attrs.Add(attr.Key, attr.Value)
- }
- // 城建属性
- for _, attr := range team.FacilityAttrs.List {
- // TODO 判断环境增加和删除属性 307 308,要转成战斗对应的攻防血的属性
- attrs.Add(attr.Key, attr.Value)
- }
- // TODO 地图增加的属性
- // 处理英雄属性和小兵属性
- heroAttrs, troopsAttrs := handleHeroAndSoldierAttr(fighter.ConfigID, attrs)
- // 设置英雄属性
- fighter.Attrs = heroAttrs
- // 没有小兵的小队不参与战斗
- if teamMemeber.TroopsNum > 0 {
- // 构建一个战斗小队
- param.Param.Squads = append(param.Param.Squads, &pb.BattleSquadParam{
- TileIndex: teamMemeber.Index,
- Fighter: playerHeroToFighter(fighter),
- TroopsID: teamMemeber.TroopsID,
- TroopsNum: teamMemeber.TroopsNum,
- FirstTroopsNum: teamMemeber.TroopsNum, // 增加首战兵力值,连战过程不变(避免PVE连续战斗英雄衰减)
- HeroTroopNum: teamMemeber.TroopsLimit,
- Attrs: troopsAttrs, // 设置小兵属性
- })
- }
- // 记录英雄在队伍中的位置
- param.HeroTeamIndex.Value[fighter.Id] = teamMemeber.TeamIndex
- }
- }
- return param
- }
- // 逐个构建玩家战斗参数
- ret := map[int64]*pb.ServerBattleCampParam{}
- for _, battleTeamData := range battleTeamDataList {
- ret[battleTeamData.PlayerID] = buildCampParam(battleTeamData)
- }
- return ret, true
- }
- // pb.PlayerHero to pb.Fighter
- func playerHeroToFighter(fighter *pb.PlayerHero) *pb.Fighter {
- if fighter == nil {
- return nil
- }
- return &pb.Fighter{
- Id: fighter.Id,
- BattleObjType: enum.BattleObjType_Hero,
- ConfigID: fighter.ConfigID,
- Attrs: fighter.Attrs,
- Skills: fighter.Skills,
- Level: fighter.Level,
- Star: fighter.Star,
- }
- }
- // 根据战斗参数构建战斗结果
- func BuildValidationResult(battleParam *pb.BattleParam) *pb.BattleResult {
- winCamp := int32(enum.BattleResult_Draw)
- // 先判断左边的进攻方,这就代表同样是双方都是没有兵了,进攻方判输
- for _, camp := range battleParam.Camps {
- if camp.Camp == enum.BattleCampType_Left && len(camp.Squads) < 1 {
- winCamp = enum.BattleCampType_Right
- break
- }
- if camp.Camp == enum.BattleCampType_Right && len(camp.Squads) < 1 {
- winCamp = enum.BattleCampType_Left
- break
- }
- }
- ret := &pb.BattleResult{
- VictoriousCamp: winCamp,
- CampResult: buildCampResult(battleParam.Camps),
- }
- return ret
- }
- // 构建战斗结果
- func buildCampResult(Camps []*pb.BattleCampParam) []*pb.BattleCampResult {
- var result []*pb.BattleCampResult
- for _, camp := range Camps {
- var squadResults []*pb.BattleSquadResult
- for _, squad := range camp.Squads {
- squadResults = append(squadResults, &pb.BattleSquadResult{
- FighterResult: &pb.FighterResult{
- Id: squad.Fighter.Id,
- BattleObjType: squad.Fighter.BattleObjType,
- ConfigID: squad.Fighter.ConfigID,
- IsDead: squad.Fighter.IsDead,
- Hp: constant.RatioBase, // 默认满血
- },
- SoldierNum: squad.TroopsNum, // 剩余兵
- })
- }
- result = append(result, &pb.BattleCampResult{
- Camp: camp.Camp,
- PlayerID: camp.PlayerID,
- SquadResults: squadResults,
- })
- }
- return result
- }
|