player_battle.go 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108
  1. package mapPlayer
  2. import (
  3. "f1-game/internal/battle"
  4. "f1-game/internal/code"
  5. "f1-game/internal/component/redis"
  6. "f1-game/internal/constant"
  7. "f1-game/internal/data"
  8. "f1-game/internal/enum"
  9. "f1-game/internal/event"
  10. "f1-game/internal/extend/math"
  11. nameActor "f1-game/internal/name/actor"
  12. nameLocal "f1-game/internal/name/local"
  13. nameRemote "f1-game/internal/name/remote"
  14. nameRoute "f1-game/internal/name/route"
  15. "f1-game/internal/pb"
  16. "f1-game/internal/sessions"
  17. "f1-game/internal/types"
  18. ao "f1-game/nodes/game/player/asset/origin"
  19. mapCall "f1-game/nodes/map/internal/call"
  20. "f1-game/nodes/map/internal/db"
  21. dbBattleReport "f1-game/nodes/map/internal/db/data/battle_report"
  22. "sort"
  23. cherryNUID "github.com/cherry-game/cherry/extend/nuid"
  24. ctime "github.com/cherry-game/cherry/extend/time"
  25. cfacade "github.com/cherry-game/cherry/facade"
  26. clog "github.com/cherry-game/cherry/logger"
  27. cproto "github.com/cherry-game/cherry/net/proto"
  28. )
  29. func (p *actorPlayer) initBattle() {
  30. p.Local().Register(nameLocal.MapPlayer_ReportInfo, p.reportInfo)
  31. p.Local().Register(nameLocal.MapPlayer_ReportList, p.reportList)
  32. p.Local().Register(nameLocal.MapPlayer_ReportLike, p.reportLike)
  33. p.Local().Register(nameLocal.MapPlayer_ReportRead, p.reportRead)
  34. p.Remote().Register(nameRemote.MapPlayer_BattleStart, p.battleStart)
  35. p.Remote().Register(nameRemote.MapPlayer_ReportAdd, p.reportAdd)
  36. // 初始化战斗处理函数
  37. p.initBattleHandler()
  38. }
  39. func (p *actorPlayer) initBattleHandler() {
  40. p.battleHandlers = map[enum.TileBattleType]func(*pb.BattleReq, *pb.BattleRsp){
  41. enum.TileBattle_PVP_Meet: p.battleStartPlayer,
  42. enum.TileBattle_PVP_Occupy: p.battleStartPlayer,
  43. enum.TileBattle_PVE_Occupy: p.battleStartRes,
  44. enum.TileBattle_PVE_SiegeGarrison: p.battleStartSiegePVE,
  45. enum.TileBattle_PVE_SiegeDurability: p.battleStartSiegePVE,
  46. enum.TileBattle_PVP_Siege: p.battleStartPlayer,
  47. enum.TileBattle_PVE_SnatchGarrison: p.battleStartSiegePVE,
  48. enum.TileBattle_PVP_Snatch: p.battleStartPlayer,
  49. enum.TileBattle_PVE_Snatch: p.battleStartSiegePVE,
  50. enum.TileBattle_PVE_Crusade: p.battleStartMonster,
  51. enum.TileBattle_PVN_SiegeDurability: p.battleStartSiegePVN,
  52. enum.TileBattle_PVN_Snatch: p.battleStartSiegePVN,
  53. }
  54. }
  55. // 个人战报列表
  56. func (p *actorPlayer) reportList(session *cproto.Session, req *pb.I32Str) {
  57. var (
  58. mapPlayerReportTable = db.GetMapPlayerReportTable(p.playerID)
  59. category = enum.ReportCategory(req.Key) // 分类 0:全部 1:开拓 2:交战 3:侦察
  60. groupID = req.Value // 战报组ID
  61. rsp = &pb.ReportGroupList{}
  62. )
  63. // 根据分类和战报组ID来精确获取战报组列表
  64. reportGroups := dbBattleReport.FilterReportGroups(mapPlayerReportTable.BattleReport.Groups, category, groupID, data.Const.MapBattleReportPage)
  65. if len(reportGroups) == 0 {
  66. p.Response(session, rsp)
  67. return
  68. }
  69. var (
  70. groupIDList []string // 战报组ID列表
  71. reportIDList []string // 战报ID列表
  72. )
  73. for _, group := range reportGroups {
  74. groupIDList = append(groupIDList, group.GroupID)
  75. }
  76. // 批量获取战报组ID对应的战报ID列表
  77. reportIDMap := redis.Game.GetReportIDListOfGroup(groupIDList...)
  78. for _, ids := range reportIDMap {
  79. reportIDList = append(reportIDList, ids...)
  80. }
  81. // 从redis批量获取战报集合
  82. reportMap := redis.Game.GetBattleReports(reportIDList...)
  83. for _, group := range reportGroups {
  84. groupProto := group.ToProto(reportIDMap, reportMap, false)
  85. if groupProto == nil {
  86. continue
  87. }
  88. rsp.List = append(rsp.List, groupProto)
  89. }
  90. p.Response(session, rsp)
  91. }
  92. // 个人收藏战报和未读数量信息
  93. func (p *actorPlayer) reportInfo(session *cproto.Session, _ *pb.None) {
  94. var (
  95. mapPlayerReportTable = db.GetMapPlayerReportTable(p.playerID)
  96. groupIDList []string // 战报组ID列表
  97. reportIDList []string // 战报ID列表
  98. )
  99. likes := mapPlayerReportTable.BattleReport.Likes
  100. for _, like := range likes {
  101. switch like.LikeType {
  102. case enum.ReportLikeType_Group:
  103. groupIDList = append(groupIDList, like.LikeID)
  104. case enum.ReportLikeType_Report:
  105. reportIDList = append(reportIDList, like.LikeID)
  106. }
  107. }
  108. // 获取战报组ID对应的战报ID列表
  109. groupReportIDMap := redis.Game.GetReportIDListOfGroup(groupIDList...)
  110. for _, ids := range groupReportIDMap {
  111. reportIDList = append(reportIDList, ids...)
  112. }
  113. // 从redis批量获取战报信息
  114. reportIDMap := redis.Game.GetBattleReports(reportIDList...)
  115. var likeList []*pb.ReportLike
  116. for _, like := range likes {
  117. var reportList []*pb.Report
  118. switch like.LikeType {
  119. case enum.ReportLikeType_Group:
  120. for _, reportID := range groupReportIDMap[like.LikeID] {
  121. if report, ok := reportIDMap[reportID]; ok {
  122. reportList = append(reportList, report)
  123. }
  124. }
  125. case enum.ReportLikeType_Report:
  126. if report, ok := reportIDMap[like.LikeID]; ok {
  127. reportList = append(reportList, report)
  128. }
  129. }
  130. if len(reportList) == 0 {
  131. continue
  132. }
  133. if len(reportList) > 1 {
  134. sort.Slice(reportList, func(i, j int) bool {
  135. return reportList[i].Time > reportList[j].Time
  136. })
  137. }
  138. likeList = append(likeList, like.ToProto(reportList))
  139. }
  140. p.Response(session, &pb.ReportInfo{
  141. Count: mapPlayerReportTable.BattleReport.GetUnreadCount(),
  142. List: likeList,
  143. })
  144. }
  145. // 收藏战报或取消收藏
  146. func (p *actorPlayer) reportLike(session *cproto.Session, req *pb.ReportLikeRequest) {
  147. if req.LikeType < 0 || req.LikeID == "" {
  148. p.ResponseCode(session, code.IllegalArgument)
  149. return
  150. }
  151. var (
  152. mapPlayerReportTable = db.GetMapPlayerReportTable(p.playerID)
  153. likeType = enum.ReportLikeType(req.LikeType) // 收藏类型 1:战报组 2:单个战报
  154. likeID = req.LikeID // 收藏ID
  155. isLike = req.IsLike // 是否收藏 true:收藏 false:取消收藏
  156. )
  157. if !isLike { // 取消收藏
  158. if mapPlayerReportTable.BattleReport.IsLike(likeType, likeID) {
  159. mapPlayerReportTable.BattleReport.RemoveLike(likeType, likeID)
  160. mapPlayerReportTable.Save2Queue()
  161. }
  162. p.ResponseCode(session, code.OK)
  163. return
  164. }
  165. // 检查战报组或战报是否存在
  166. switch likeType {
  167. case enum.ReportLikeType_Group:
  168. if len(redis.Game.GetReportIDListOfGroup(likeID)) == 0 {
  169. p.ResponseCode(session, code.BattleReportNotExist)
  170. return
  171. }
  172. case enum.ReportLikeType_Report:
  173. if len(redis.Game.GetBattleReports(likeID)) == 0 {
  174. p.ResponseCode(session, code.BattleReportNotExist)
  175. return
  176. }
  177. default:
  178. p.ResponseCode(session, code.IllegalArgument)
  179. return
  180. }
  181. // 已经收藏过
  182. if mapPlayerReportTable.BattleReport.IsLike(likeType, likeID) {
  183. p.ResponseCode(session, code.OK)
  184. return
  185. }
  186. mapPlayerReportTable.BattleReport.AddLike(likeType, likeID, ctime.Now().ToSecond())
  187. mapPlayerReportTable.Save2Queue()
  188. p.ResponseCode(session, code.OK)
  189. }
  190. // 战报一键阅读
  191. func (p *actorPlayer) reportRead(session *cproto.Session, req *pb.Str) {
  192. var (
  193. mapPlayerReportTable = db.GetMapPlayerReportTable(p.playerID)
  194. groupID = req.Value // 战报组ID 0:全部
  195. )
  196. ok := mapPlayerReportTable.BattleReport.Read(groupID)
  197. if !ok {
  198. p.ResponseCode(session, code.BattleReportNotExist)
  199. return
  200. }
  201. mapPlayerReportTable.Save2Queue()
  202. p.ResponseCode(session, code.OK)
  203. }
  204. // 增加战报组
  205. func (p *actorPlayer) reportAdd(reportGroup *pb.ReportGroup) {
  206. if len(reportGroup.List) < 1 {
  207. return
  208. }
  209. // 战报放入redis
  210. redis.Game.AddReports(reportGroup.GroupID, reportGroup.List...)
  211. // 添加个人战报记录
  212. mapPlayerReportTable := db.GetMapPlayerReportTable(p.playerID)
  213. dbBattleReport.AddReportGroup(&mapPlayerReportTable.BattleReport.Groups, reportGroup.GroupID, enum.ReportCategory(reportGroup.Category), reportGroup.List[0].Time)
  214. mapPlayerReportTable.Save2Queue()
  215. // 通知客户端
  216. sessions.PushPlayer(p.playerID, nameRoute.PushMapPlayer_ReportNew, reportGroup)
  217. playerTable := db.GetMapPlayerTable(p.playerID)
  218. if playerTable != nil && playerTable.LeagueID > 0 && !enum.IsDurabilityBattle(enum.TileBattleType(reportGroup.List[0].BattleType)) {
  219. // 添加联盟战报
  220. mapCall.League.BattleReportAdd(playerTable.LeagueID, reportGroup)
  221. }
  222. // 发送战斗结果事件
  223. p.PostEvent(event.NewBattleResult(p.playerID, reportGroup))
  224. }
  225. // 根据玩家ID从战斗参数里获取玩家部队信息
  226. func getTeamDataByPlayerID(battleReq *pb.BattleReq, playerID int64) *pb.BattleTeamData {
  227. for _, attacker := range battleReq.Attackers {
  228. if attacker.PlayerID == playerID {
  229. return attacker
  230. }
  231. }
  232. return battleReq.Defender
  233. }
  234. // 构建战报
  235. func (p *actorPlayer) buildReportGroup(battleReq *pb.BattleReq, attacker *pb.BattleCampParam, defender *pb.BattleCampParam,
  236. resultVerify *pb.BattleResult, isDefender bool, recordID string, feat int64) *pb.ReportGroup {
  237. attackerTeamData := getTeamDataByPlayerID(battleReq, attacker.PlayerID)
  238. defenderTeamData := getTeamDataByPlayerID(battleReq, defender.PlayerID)
  239. report := &pb.Report{
  240. ReportID: cherryNUID.Next(),
  241. BattleType: battleReq.BattleType,
  242. Point: battleReq.Tile.Point,
  243. ConfigID: battleReq.Tile.ObjectConfigID,
  244. Time: ctime.Now().ToMillisecond(),
  245. BattleRecordID: recordID,
  246. Attacker: buildReportTeam(attackerTeamData, attacker, resultVerify),
  247. Defender: buildReportTeam(defenderTeamData, defender, resultVerify),
  248. IsDefender: isDefender,
  249. }
  250. var (
  251. groupID = attackerTeamData.ReportGroupID // 战报组
  252. result = resultVerify.VictoriousCamp // TODO 战斗结果 战斗验证逻辑会有平局,但是现有业务逻辑判断只有胜负
  253. )
  254. if isDefender { // 防守方取反
  255. switch resultVerify.VictoriousCamp {
  256. case enum.BattleCampType_Left:
  257. result = enum.BattleCampType_Right
  258. case enum.BattleCampType_Right:
  259. result = enum.BattleCampType_Left
  260. }
  261. groupID = battleReq.Defender.ReportGroupID
  262. }
  263. // 战斗结果 0:赢
  264. report.Result = result
  265. // 扣除耐久
  266. report.ReportResult = &pb.ReportResult{
  267. Rewards: &pb.AssetList{
  268. List: []*pb.Asset{},
  269. },
  270. }
  271. // 进攻方赢,防守方输才显示战果,耐久,资源
  272. if isDefender {
  273. if result != enum.BattleCampType_Left {
  274. // 被扣除了耐久
  275. report.ReportResult.Durability = battleReq.Durability
  276. // 被掠夺了资源
  277. if battleReq.Rewards != nil {
  278. for _, reward := range battleReq.Rewards.List {
  279. report.ReportResult.Rewards.List = append(report.ReportResult.Rewards.List, reward)
  280. }
  281. }
  282. }
  283. } else {
  284. if result == enum.BattleCampType_Left {
  285. // 扣除耐久
  286. report.ReportResult.Durability = battleReq.Durability
  287. // 获得的资源
  288. if battleReq.Rewards != nil {
  289. for _, reward := range battleReq.Rewards.List {
  290. report.ReportResult.Rewards.List = append(report.ReportResult.Rewards.List, reward)
  291. }
  292. }
  293. }
  294. }
  295. // 功勋
  296. if feat > 0 {
  297. report.ReportResult.Rewards.List = append(report.ReportResult.Rewards.List, &pb.Asset{
  298. Id: enum.ItemIDFeat,
  299. Num: feat,
  300. })
  301. }
  302. // TODO 战报分类 新增侦察类型再进行判断处理
  303. category := enum.ReportCategory_PVE
  304. if enum.IsTilePVPBattle(enum.TileBattleType(battleReq.BattleType)) {
  305. category = enum.ReportCategory_PVP
  306. }
  307. // 如果地图没有给战报组ID,则生成一个唯一的战报组ID避免战报无法记录,最多就是单独的一个战报
  308. if len(groupID) == 0 {
  309. groupID = cherryNUID.Next()
  310. }
  311. return &pb.ReportGroup{
  312. GroupID: groupID,
  313. Category: int32(category),
  314. List: []*pb.Report{report},
  315. }
  316. }
  317. func buildReportTeam(battleTeamData *pb.BattleTeamData, camp *pb.BattleCampParam, resultVerify *pb.BattleResult) *pb.BattleReportTeam {
  318. if camp == nil {
  319. return nil
  320. }
  321. team := &pb.BattleReportTeam{}
  322. for _, squad := range camp.Squads {
  323. squadResult := battle.GetBattleSquadResult(camp.Camp, squad.Fighter.Id, resultVerify)
  324. team.List = append(team.List, &pb.BattleReportObject{
  325. Id: squad.Fighter.Id,
  326. ObjConfigID: squad.Fighter.ConfigID,
  327. BattleObjType: squad.Fighter.BattleObjType,
  328. Level: squad.Fighter.Level,
  329. Star: squad.Fighter.Star,
  330. LineupID: squad.TileIndex,
  331. IsDead: squadResult.FighterResult.IsDead,
  332. TroopsID: squad.TroopsID,
  333. TotalSoldier: squad.TroopsNum,
  334. RemainSoldier: squadResult.SoldierNum,
  335. MaxHealth: battle.GetMaxHealth(squad.Fighter.Attrs),
  336. Health: squadResult.FighterResult.Hp,
  337. })
  338. }
  339. // 玩家信息
  340. if battleTeamData != nil {
  341. team.PlayerID = battleTeamData.PlayerID
  342. team.PlayerName = battleTeamData.PlayerName
  343. team.LeagueID = battleTeamData.LeagueID
  344. team.LeagueName = battleTeamData.LeagueName
  345. }
  346. return team
  347. }
  348. // 守军战斗逻辑, 返回是否胜利
  349. func (p *actorPlayer) onBattleDefend(req *pb.BattleReq, attackerServerParam *pb.ServerBattleCampParam, teamBattleDamage *pb.TeamBattleDamage, defender *pb.BattleCampParam, monsterIndex int32) (int64, bool) {
  350. var (
  351. playerTable = db.GetMapPlayerTable(p.playerID)
  352. attacker = attackerServerParam.Param
  353. )
  354. // 设置守军参数
  355. setDefendBattleParam(playerTable, req.Tile.ObjectID, monsterIndex, defender)
  356. // 判断守军是否还能参战,兵力都为0就跳过
  357. if checkDefendSoldier(playerTable, req.Tile.ObjectID, monsterIndex) {
  358. return 0, true
  359. }
  360. // 录像ID
  361. recordID := cherryNUID.Next()
  362. // 战斗验证
  363. resultVerify, isVerify := p.battleVerify(recordID, attacker, defender)
  364. if resultVerify == nil {
  365. return 0, false
  366. }
  367. // 如果不是战斗验证的没有录像
  368. if !isVerify {
  369. recordID = ""
  370. }
  371. // 构建战报
  372. reportGroup := p.buildReportGroup(req, attacker, defender, resultVerify, false, recordID, 0)
  373. p.reportAdd(reportGroup)
  374. // 如果打的地块有归属也需要构建对方战报
  375. if req.Defender.PlayerID > 0 {
  376. defenderReport := p.buildReportGroup(req, defender, attacker, resultVerify, true, recordID, 0)
  377. mapCall.Player.BattleReportAdd(req.Defender.PlayerID, defenderReport)
  378. }
  379. // 更新进攻方兵力
  380. battle.UpdateBattleParam(attacker, resultVerify)
  381. // 战斗结束后保存守军信息
  382. recoverTime := saveDefendStatus(playerTable, req.Tile.ObjectID, monsterIndex, resultVerify)
  383. // 记录进攻方剩余兵力
  384. recordBattleSoldierNum(teamBattleDamage, attackerServerParam.HeroTeamIndex, battle.GetBattleCampResult(resultVerify, enum.BattleCampType_Left))
  385. return recoverTime, resultVerify.VictoriousCamp == enum.BattleCampType_Left
  386. }
  387. // 战斗验证
  388. func (p *actorPlayer) battleVerify(recordID string, attacker *pb.BattleCampParam, defender *pb.BattleCampParam) (*pb.BattleResult, bool) {
  389. // 构建战斗参数
  390. param := &pb.BattleParam{
  391. MapID: 2, //TODO 根据战斗的类型配置不同的战斗地图ID
  392. RandomSeed: uint64(ctime.Now().ToMillisecond()),
  393. BattleType: enum.BattleType_SLG,
  394. Camps: []*pb.BattleCampParam{
  395. attacker,
  396. defender,
  397. },
  398. GenerateConfigData: true, // 设置生成配置数据,战斗验证结果会返回配置数据
  399. }
  400. // 只要双方有战斗小队数量为0直接构建战斗结果
  401. if !battle.CheckBattleSquadTroopsNum(attacker) || !battle.CheckBattleSquadTroopsNum(defender) {
  402. return battle.BuildValidationResult(param), false
  403. }
  404. // 验证战斗结果
  405. return battle.Validation(recordID, param), true
  406. }
  407. func (p *actorPlayer) battleStart(req *pb.BattleReq) {
  408. rsp := &pb.BattleRsp{
  409. Data: req,
  410. }
  411. battleType := enum.TileBattleType(req.BattleType)
  412. if fn, ok := p.battleHandlers[battleType]; ok {
  413. fn(req, rsp)
  414. } else {
  415. p.Error("battleStart battleType = %d, not found", battleType)
  416. }
  417. // 打耐久的战斗不需要返回,只需要记录战报
  418. if battleType != enum.TileBattle_PVN_SiegeDurability {
  419. // 把战斗结果返回给map
  420. mapCall.Logic.BattleEnd(rsp)
  421. }
  422. }
  423. // battleStartSiegePVE 与攻城守军战斗
  424. func (p *actorPlayer) battleStartSiegePVE(req *pb.BattleReq, rsp *pb.BattleRsp) {
  425. // 批量构建进攻方战斗数据
  426. battleParamMap, ok := battle.GetServerBattleCampParams(req)
  427. if !ok {
  428. p.Error("battleStartSiegePVE GetServerBattleCampParams error")
  429. return
  430. }
  431. if len(req.Tile.Monsters) == 0 {
  432. p.Warn("battleStartSiegePVE failed. tileID = %d, monsters is empty", req.Tile.Id)
  433. return
  434. }
  435. monsterGroupID := req.Tile.Monsters[0]
  436. defender := data.BuildBattleCampParam(monsterGroupID, enum.BattleCampType_Right)
  437. if len(defender.Squads) == 0 {
  438. p.Warn("battleStartSiegePVE failed. tileID = %d, monsterGroupID = %d", req.Tile.Id, monsterGroupID)
  439. return
  440. }
  441. // 构建进攻方数据
  442. var (
  443. attackerTeamData = req.Attackers[0]
  444. attackerServerParam = battleParamMap[attackerTeamData.PlayerID]
  445. attacker = attackerServerParam.Param
  446. attackerTeamBattleDamage = battle.NewTeamBattleDamage(attackerTeamData.TeamID, attackerTeamData.ObjectID)
  447. )
  448. // 记录战斗之前的兵力
  449. recordBattleTroops(attackerTeamBattleDamage, attackerServerParam)
  450. // 录像ID
  451. recordID := cherryNUID.Next()
  452. // 战斗验证
  453. resultVerify, isVerify := p.battleVerify(recordID, attacker, defender)
  454. if resultVerify == nil {
  455. p.Warn("battleStartSiegePVE verify failed. tileID = %d, monsterGroupID = %d", req.Tile.Id, monsterGroupID)
  456. return
  457. }
  458. // 如果不是战斗验证的没有录像
  459. if !isVerify {
  460. recordID = ""
  461. }
  462. // 构建战报
  463. reportGroup := p.buildReportGroup(req, attacker, defender, resultVerify, false, recordID, 0)
  464. if p.playerID == attackerTeamData.PlayerID {
  465. p.reportAdd(reportGroup)
  466. } else {
  467. mapCall.Player.BattleReportAdd(attackerTeamData.PlayerID, reportGroup)
  468. }
  469. // 如果打建筑有归属也需要构建对方战报
  470. if req.Defender.PlayerID > 0 || req.Defender.LeagueID > 0 {
  471. defenderReport := p.buildReportGroup(req, defender, attacker, resultVerify, true, "", 0)
  472. if req.Defender.PlayerID > 0 {
  473. mapCall.Player.BattleReportAdd(req.Defender.PlayerID, defenderReport)
  474. }
  475. if req.Defender.LeagueID > 0 {
  476. mapCall.League.BattleReportAdd(req.Defender.LeagueID, defenderReport)
  477. }
  478. }
  479. // 记录进攻方剩余兵力
  480. recordBattleSoldierNum(attackerTeamBattleDamage, attackerServerParam.HeroTeamIndex, battle.GetBattleCampResult(resultVerify, enum.BattleCampType_Left))
  481. // 更新进攻方兵力
  482. battle.UpdateBattleParam(attacker, resultVerify)
  483. // 更新防守方兵力
  484. troopLoss := battle.UpdateBattleParam(defender, resultVerify)
  485. if troopLoss > 0 {
  486. attackerTeamBattleDamage.KillTroops += troopLoss
  487. }
  488. // 把战损扣除的兵力带回去
  489. rsp.MarchTroops = append(rsp.MarchTroops, convertMarchSyncTroops(attackerTeamBattleDamage))
  490. // 兵力扣除和更新
  491. p.subTeamTroopsNum(attackerTeamBattleDamage)
  492. rsp.IsWin = resultVerify.VictoriousCamp == enum.BattleCampType_Left
  493. if !rsp.IsWin {
  494. // 失败设置重伤
  495. var (
  496. injuresTime = ctime.Now().ToSecond() + data.Const.GetHeroInjureTime()
  497. arg = &pb.I32I64{Key: attackerTeamData.TeamID, Value: injuresTime}
  498. )
  499. p.setTeamInjury(arg)
  500. }
  501. }
  502. // battleStartSiegePVN 无守军的拆耐久战
  503. func (p *actorPlayer) battleStartSiegePVN(req *pb.BattleReq, rsp *pb.BattleRsp) {
  504. // 批量构建进攻方战斗数据
  505. battleParamMap, ok := battle.GetServerBattleCampParams(req)
  506. if !ok {
  507. p.Error("battleStartSiegePVN GetServerBattleCampParams error")
  508. return
  509. }
  510. // 构建一个没有小队的敌方阵营战斗参数
  511. defender := &pb.BattleCampParam{
  512. Camp: enum.BattleCampType_Right,
  513. Squads: []*pb.BattleSquadParam{},
  514. }
  515. // 构建进攻方数据
  516. var (
  517. attackerTeamData = req.Attackers[0]
  518. attackerServerParam = battleParamMap[attackerTeamData.PlayerID]
  519. attacker = attackerServerParam.Param
  520. )
  521. // 战斗验证,走自动构建的战斗结果
  522. resultVerify, _ := p.battleVerify("", attacker, defender)
  523. // 构建战报
  524. reportGroup := p.buildReportGroup(req, attacker, defender, resultVerify, false, "", 0)
  525. p.reportAdd(reportGroup)
  526. // 建筑有归属也需要构建对方战报,互斥,优先判断归属是玩家
  527. if req.Defender.PlayerID > 0 || req.Defender.LeagueID > 0 {
  528. defenderReport := p.buildReportGroup(req, defender, attacker, resultVerify, true, "", 0)
  529. if req.Defender.PlayerID > 0 {
  530. mapCall.Player.BattleReportAdd(req.Defender.PlayerID, defenderReport)
  531. } else if req.Defender.LeagueID > 0 {
  532. // 联盟建筑,单独添加到对应联盟战报里去
  533. mapCall.League.BattleReportAdd(req.Defender.LeagueID, defenderReport)
  534. }
  535. }
  536. }
  537. // battleStartPlayer 玩家交战
  538. func (p *actorPlayer) battleStartPlayer(req *pb.BattleReq, rsp *pb.BattleRsp) {
  539. battleParamMap, ok := battle.GetServerBattleCampParams(req)
  540. if !ok {
  541. p.Error("battleStartPlayer GetServerBattleCampParams error")
  542. return
  543. }
  544. var (
  545. // 进攻方的战斗数据实时构建
  546. attackerTeamData = req.Attackers[0]
  547. attacker = battleParamMap[attackerTeamData.PlayerID]
  548. attackerBattleDamage = battle.NewTeamBattleDamage(attackerTeamData.TeamID, attackerTeamData.ObjectID)
  549. // 对方战斗数据从redis中获取
  550. defender = battleParamMap[req.Defender.PlayerID]
  551. defenderBattleDamage = battle.NewTeamBattleDamage(req.Defender.TeamID, req.Defender.ObjectID)
  552. )
  553. recordBattleTroops(attackerBattleDamage, attacker)
  554. recordBattleTroops(defenderBattleDamage, defender)
  555. // 录像ID
  556. recordID := cherryNUID.Next()
  557. // 战斗验证
  558. resultVerify, isVerify := p.battleVerify(recordID, attacker.Param, defender.Param)
  559. if resultVerify == nil {
  560. return
  561. }
  562. // 如果不是战斗验证的没有录像
  563. if !isVerify {
  564. recordID = ""
  565. }
  566. // 更新进攻方剩余兵力
  567. recordBattleSoldierNum(attackerBattleDamage, attacker.HeroTeamIndex, battle.GetBattleCampResult(resultVerify, enum.BattleCampType_Left))
  568. // 更新防守方剩余兵力
  569. recordBattleSoldierNum(defenderBattleDamage, defender.HeroTeamIndex, battle.GetBattleCampResult(resultVerify, enum.BattleCampType_Right))
  570. // 攻击方功勋
  571. attackerFeat := calAndSendBattleGetFeatNum(attacker, attackerBattleDamage)
  572. // 防守方功勋
  573. defenderFeat := calAndSendBattleGetFeatNum(defender, defenderBattleDamage)
  574. // 构建战报
  575. reportGroup := p.buildReportGroup(req, attacker.Param, defender.Param, resultVerify, false, recordID, attackerFeat)
  576. // 给进攻方发送战报
  577. p.reportAdd(reportGroup)
  578. // 构建对方战报
  579. defenderReport := p.buildReportGroup(req, defender.Param, attacker.Param, resultVerify, true, recordID, defenderFeat)
  580. // 给对方发送战报
  581. mapCall.Player.BattleReportAdd(req.Defender.PlayerID, defenderReport)
  582. // 更新进攻方兵力
  583. battle.UpdateBattleParam(attacker.Param, resultVerify)
  584. // 更新防守方兵力
  585. battle.UpdateBattleParam(defender.Param, resultVerify)
  586. // 通知更新进攻方兵力
  587. p.subTeamTroopsNum(attackerBattleDamage)
  588. // 通知更新防守兵力变化
  589. mapCall.Player.SubTeamTroopsNum(req.Defender.PlayerID, defenderBattleDamage)
  590. // 把战损扣除的兵力带回去
  591. rsp.MarchTroops = append(rsp.MarchTroops, convertMarchSyncTroops(attackerBattleDamage), convertMarchSyncTroops(defenderBattleDamage))
  592. if resultVerify.VictoriousCamp == enum.BattleCampType_Left {
  593. rsp.IsWin = true
  594. }
  595. // 重伤时间
  596. var injuresTime = ctime.Now().ToSecond() + data.Const.GetHeroInjureTime()
  597. if rsp.IsWin {
  598. // 胜利设置对方玩家重伤
  599. arg := &pb.I32I64{Key: req.Defender.TeamID, Value: injuresTime}
  600. targetPath := cfacade.NewChildPath("", nameActor.Map_Player, req.Defender.PlayerID)
  601. p.Call(targetPath, nameRemote.MapPlayer_SetTeamInjury, arg)
  602. } else {
  603. // 失败设置重伤
  604. arg := &pb.I32I64{Key: attackerTeamData.TeamID, Value: injuresTime}
  605. p.setTeamInjury(arg)
  606. }
  607. }
  608. // 单人和多人组队讨伐
  609. func (p *actorPlayer) battleStartMonster(req *pb.BattleReq, rsp *pb.BattleRsp) {
  610. if len(req.Tile.Monsters) == 0 {
  611. p.Warn("battle start monster failed, monster is empty. resID = %d", req.Tile.Id)
  612. return
  613. }
  614. // 批量构建进攻方战斗数据
  615. battleParamMap, ok := battle.GetServerBattleCampParams(req)
  616. if !ok {
  617. p.Error("battleStartMonster GetServerBattleCampParams error")
  618. return
  619. }
  620. // 构建进攻方数据
  621. teamBattleDamageMap := map[int64]*pb.TeamBattleDamage{}
  622. for _, attacker := range req.Attackers {
  623. attackerBattleParam, found := battleParamMap[attacker.PlayerID]
  624. if found {
  625. teamBattleDamage := battle.NewTeamBattleDamage(attacker.TeamID, attacker.ObjectID)
  626. // 记录战斗之前的兵力
  627. recordBattleTroops(teamBattleDamage, attackerBattleParam)
  628. // 记录所有玩家的部队战斗兵力数据,战前兵力和剩余兵力
  629. teamBattleDamageMap[attacker.PlayerID] = teamBattleDamage
  630. } else {
  631. p.Warn("battle start monster failed, player not found. playerID = %d", attacker.PlayerID)
  632. }
  633. }
  634. var (
  635. attackerIndex int = 0 // 按照顺序打,打完一个接着打,输了就给下一个继续打
  636. )
  637. for index, monsterGroupID := range req.Tile.Monsters {
  638. defender := data.BuildBattleCampParam(monsterGroupID, enum.BattleCampType_Right)
  639. if len(defender.Squads) == 0 {
  640. p.Warn("battle start monster failed. tileID = %d, monsterGroupID = %d", req.Tile.Id, monsterGroupID)
  641. return
  642. }
  643. for i := attackerIndex; i < len(req.Attackers); i++ {
  644. var (
  645. attacker = req.Attackers[i] // 进攻方请求数据
  646. attackerParam = battleParamMap[attacker.PlayerID] // 进攻方战斗数据
  647. attackerBattleDemage = teamBattleDamageMap[attacker.PlayerID] // 进攻方战损记录
  648. )
  649. isWin := p.onBattleMonster(req, attackerParam, attackerBattleDemage, defender, monsterGroupID)
  650. // 打赢了就跳出循环打下一个怪
  651. if isWin {
  652. // 全部打完了
  653. if index == len(req.Tile.Monsters)-1 {
  654. if isWin {
  655. rsp.IsWin = true
  656. }
  657. // 获取进攻方兵力
  658. if p.playerID == attacker.PlayerID {
  659. p.subTeamTroopsNum(attackerBattleDemage)
  660. } else {
  661. // 给对方发送兵力变化
  662. mapCall.Player.SubTeamTroopsNum(attacker.PlayerID, attackerBattleDemage)
  663. }
  664. }
  665. break
  666. } else {
  667. if p.playerID == attacker.PlayerID {
  668. p.subTeamTroopsNum(attackerBattleDemage)
  669. } else {
  670. // 给对方发送兵力变化
  671. mapCall.Player.SubTeamTroopsNum(attacker.PlayerID, attackerBattleDemage)
  672. }
  673. var (
  674. injuresTime = ctime.Now().ToSecond() + data.Const.GetHeroInjureTime()
  675. arg = &pb.I32I64{Key: attacker.TeamID, Value: injuresTime}
  676. )
  677. // 如果打输了,就设置重伤状态
  678. if p.playerID == attacker.PlayerID {
  679. p.setTeamInjury(arg)
  680. } else {
  681. targetPath := cfacade.NewChildPath("", nameActor.Map_Player, req.Defender.PlayerID)
  682. p.Call(targetPath, nameRemote.MapPlayer_SetTeamInjury, arg)
  683. }
  684. // 打输了就给下一个继续打
  685. attackerIndex++
  686. }
  687. }
  688. // 如果所有人都打完了,还没有打赢,就算输了
  689. if attackerIndex >= len(req.Attackers) {
  690. break
  691. }
  692. }
  693. // 把战损扣除的兵力带回去
  694. for _, marchSyncTroops := range teamBattleDamageMap {
  695. rsp.MarchTroops = append(rsp.MarchTroops, convertMarchSyncTroops(marchSyncTroops))
  696. }
  697. }
  698. func (p *actorPlayer) onBattleMonster(req *pb.BattleReq, attackerParam *pb.ServerBattleCampParam, attackerBattleDemage *pb.TeamBattleDamage, defender *pb.BattleCampParam, monsterGroupID int32) bool {
  699. // 进攻方战斗参数
  700. attacker := attackerParam.Param
  701. // 录像ID
  702. recordID := cherryNUID.Next()
  703. // 战斗验证
  704. resultVerify, isVerify := p.battleVerify(recordID, attacker, defender)
  705. if resultVerify == nil {
  706. p.Warn("onBattleMonster battle verify failed. tileID = %d, monsterGroupID = %d", req.Tile.Id, monsterGroupID)
  707. return false
  708. }
  709. // 如果不是战斗验证的没有录像
  710. if !isVerify {
  711. recordID = ""
  712. }
  713. // 构建战报
  714. reportGroup := p.buildReportGroup(req, attacker, defender, resultVerify, false, recordID, 0)
  715. if p.playerID == attacker.PlayerID {
  716. p.reportAdd(reportGroup)
  717. } else {
  718. mapCall.Player.BattleReportAdd(attacker.PlayerID, reportGroup)
  719. }
  720. // 记录进攻方剩余兵力
  721. recordBattleSoldierNum(attackerBattleDemage, attackerParam.HeroTeamIndex, battle.GetBattleCampResult(resultVerify, enum.BattleCampType_Left))
  722. // 更新进攻方兵力
  723. battle.UpdateBattleParam(attacker, resultVerify)
  724. // 更新防守方兵力
  725. troopLoss := battle.UpdateBattleParam(defender, resultVerify)
  726. if troopLoss > 0 {
  727. attackerBattleDemage.KillTroops += troopLoss
  728. }
  729. // 打赢了就跳出循环打下一个怪
  730. return resultVerify.VictoriousCamp == enum.BattleCampType_Left
  731. }
  732. // battleStartRes 资源守军战
  733. func (p *actorPlayer) battleStartRes(req *pb.BattleReq, rsp *pb.BattleRsp) {
  734. if len(req.Tile.Monsters) == 0 {
  735. p.Warn("battle start res failed, monster is empty. resID = %d", req.Tile.Id)
  736. return
  737. }
  738. // 批量构建进攻方战斗数据
  739. battleParamMap, ok := battle.GetServerBattleCampParams(req)
  740. if !ok {
  741. p.Error("battleStartRes GetServerBattleCampParams error")
  742. return
  743. }
  744. var (
  745. attackerTeamData = req.Attackers[0]
  746. attackerServerParam = battleParamMap[attackerTeamData.PlayerID]
  747. attackerTeamBattleDamage = battle.NewTeamBattleDamage(attackerTeamData.TeamID, attackerTeamData.ObjectID)
  748. )
  749. // 记录战斗之前的兵力
  750. recordBattleTroops(attackerTeamBattleDamage, attackerServerParam)
  751. var (
  752. isWin bool // 是否胜利
  753. recoverTime int64 // 资源恢复时间
  754. )
  755. for index, monsterGroupID := range req.Tile.Monsters {
  756. defender := data.BuildBattleCampParam(monsterGroupID, enum.BattleCampType_Right)
  757. if len(defender.Squads) == 0 {
  758. p.Warn("battleStartRes failed. tileID = %d, monsterGroupID = %d", req.Tile.Id, monsterGroupID)
  759. break
  760. }
  761. // 进入战斗
  762. retRecoverTime, retIsWin := p.onBattleDefend(req, attackerServerParam, attackerTeamBattleDamage, defender, int32(index))
  763. if retRecoverTime > 0 {
  764. recoverTime = retRecoverTime
  765. }
  766. if !retIsWin {
  767. break
  768. }
  769. // 全部打完才算赢
  770. if index == len(req.Tile.Monsters)-1 {
  771. isWin = true
  772. }
  773. }
  774. rsp.IsWin = isWin
  775. // 胜利就清空守军状态信息
  776. if rsp.IsWin {
  777. playerTable := db.GetMapPlayerTable(p.playerID)
  778. playerTable.Defends.CleanDefend(req.Tile.ObjectID)
  779. } else {
  780. // 失败则通知恢复时间
  781. if recoverTime > 0 {
  782. sessions.PushPlayer(p.playerID, nameRoute.PushMapPlayer_ResDefendRecover, &pb.I64I64{
  783. Key: req.Tile.ObjectID,
  784. Value: recoverTime,
  785. })
  786. }
  787. }
  788. // 把战损扣除的兵力带回去
  789. rsp.MarchTroops = append(rsp.MarchTroops, convertMarchSyncTroops(attackerTeamBattleDamage))
  790. // 兵力扣除和更新
  791. p.subTeamTroopsNum(attackerTeamBattleDamage)
  792. if !rsp.IsWin {
  793. var (
  794. injuresTime = ctime.Now().ToSecond() + data.Const.GetHeroInjureTime()
  795. arg = &pb.I32I64{Key: req.Attackers[0].TeamID, Value: injuresTime}
  796. )
  797. // 失败设置重伤
  798. p.setTeamInjury(arg)
  799. }
  800. }
  801. // 记录战斗之前的兵力
  802. func recordBattleTroops(teamBattleDamage *pb.TeamBattleDamage, serverParam *pb.ServerBattleCampParam) {
  803. for _, squad := range serverParam.Param.Squads {
  804. teamIndex := serverParam.HeroTeamIndex.Value[squad.Fighter.Id]
  805. teamBattleDamage.Members[teamIndex] = &pb.TeamMemberDamage{
  806. TeamIndex: teamIndex,
  807. TroopsNum: squad.TroopsNum,
  808. }
  809. }
  810. }
  811. // 记录剩余兵力
  812. func recordBattleSoldierNum(teamBattleDamage *pb.TeamBattleDamage, heroTeamIndex *pb.I64I32Map, battleCampResult *pb.BattleCampResult) {
  813. for _, marchTroops := range teamBattleDamage.Members {
  814. for _, squad := range battleCampResult.SquadResults {
  815. if heroTeamIndex.Value[squad.FighterResult.Id] == marchTroops.TeamIndex {
  816. marchTroops.SoldierNum = squad.SoldierNum
  817. break
  818. }
  819. }
  820. }
  821. }
  822. // 计算pvp战斗获得的功勋值
  823. func calAndSendBattleGetFeatNum(battle *pb.ServerBattleCampParam, damage *pb.TeamBattleDamage) int64 {
  824. playerID := battle.Param.PlayerID
  825. if playerID <= 0 {
  826. return 0
  827. }
  828. // 获得功勋值
  829. featNum := int64(0)
  830. for _, squad := range battle.Param.Squads {
  831. armTypeRow, found := data.ArmType.GetByID(squad.TroopsID)
  832. if !found {
  833. clog.Warnf("[CalBattleFeatNum] armType not found. playerID=%d, squadID=%d", battle.Param.PlayerID, squad.TroopsID)
  834. continue
  835. }
  836. armLevelRow, found := data.ArmLevel.GetByLevel(armTypeRow.Level)
  837. if !found {
  838. clog.Warnf("[CalBattleFeatNum] armLevel not found. playerID=%d, squadID=%d", battle.Param.PlayerID, squad.TroopsID)
  839. continue
  840. }
  841. // 基础值
  842. featNum += armLevelRow.BaseFeat
  843. // 计算出伤兵
  844. teamIndex := battle.HeroTeamIndex.Value[squad.Fighter.Id]
  845. memberGamage, found := damage.Members[teamIndex]
  846. if !found {
  847. clog.Warnf("[CalBattleFeatNum] memberGamage not found. playerID=%d, squadID=%d, teamIndex=%d", battle.Param.PlayerID, squad.TroopsID, teamIndex)
  848. continue
  849. }
  850. lossTroops := float64(memberGamage.TroopsNum - memberGamage.SoldierNum)
  851. // 万分比系数 向下取整
  852. featNum += math.Floor[float64, int64](lossTroops * (float64(armLevelRow.FeatRatio) / constant.FRatioBase))
  853. }
  854. if featNum <= 0 {
  855. return featNum
  856. }
  857. // 发送功勋奖励
  858. assets := types.Assets{}
  859. assets.Add(enum.ItemIDFeat, featNum)
  860. mapCall.Player.AddAssets(playerID, assets, ao.None)
  861. return featNum
  862. }
  863. // 转换成同步兵力结构
  864. func convertMarchSyncTroops(teamBattleDamage *pb.TeamBattleDamage) *pb.MarchSyncTroops {
  865. var troops []*pb.MarchTroops
  866. for _, member := range teamBattleDamage.Members {
  867. troops = append(troops, &pb.MarchTroops{
  868. TeamIndex: member.TeamIndex,
  869. TroopsNum: max(0, member.TroopsNum-member.SoldierNum),
  870. })
  871. }
  872. return &pb.MarchSyncTroops{
  873. TeamID: teamBattleDamage.TeamID,
  874. ObjectID: teamBattleDamage.ObjectID,
  875. Troops: troops,
  876. KillTroops: teamBattleDamage.KillTroops,
  877. }
  878. }
  879. // 设置守军战斗参数
  880. func setDefendBattleParam(playerTable *db.MapPlayerTable, objectID int64, monsterIndex int32, defender *pb.BattleCampParam) {
  881. var newSquads []*pb.BattleSquadParam // 新的守军列表
  882. for _, squad := range defender.Squads {
  883. defend, found := playerTable.Defends.GetDefend(objectID)
  884. if found {
  885. defendStatus, found := defend.GetDefendStatus(monsterIndex, squad.Fighter.Id)
  886. if found {
  887. // 剩余兵力
  888. squad.TroopsNum = defendStatus.RemainSoldier
  889. }
  890. }
  891. // 剩余兵力为0的就过滤掉
  892. if squad.TroopsNum > 0 {
  893. newSquads = append(newSquads, squad)
  894. }
  895. }
  896. defender.Squads = newSquads
  897. }
  898. // 检查守军剩余小兵数量是否为0
  899. func checkDefendSoldier(playerTable *db.MapPlayerTable, objectID int64, monsterIndex int32) bool {
  900. defend, found := playerTable.Defends.GetDefend(objectID)
  901. if !found {
  902. return false
  903. }
  904. // 记录是否存在
  905. var isExist bool
  906. for _, status := range defend.DefendStatusMap {
  907. if status.MonsterIndex == monsterIndex {
  908. isExist = true
  909. if status.RemainSoldier > 0 {
  910. return false
  911. }
  912. }
  913. }
  914. // 没有记录就返回false
  915. if !isExist {
  916. return false
  917. }
  918. return true
  919. }
  920. // 保存守军状态
  921. func saveDefendStatus(playerTable *db.MapPlayerTable, objectID int64, monsterIndex int32, battleResult *pb.BattleResult) int64 {
  922. var recoverTime int64
  923. for _, result := range battleResult.CampResult {
  924. if result.Camp == enum.BattleCampType_Right {
  925. for _, squad := range result.SquadResults {
  926. defend, isNew := playerTable.Defends.GetOrCreateDefend(objectID)
  927. if isNew {
  928. recoverTime = defend.RecoverTime
  929. }
  930. defend.UpdateDefendStatus(monsterIndex, squad.FighterResult.Id, squad.SoldierNum)
  931. }
  932. }
  933. }
  934. return recoverTime
  935. }