player_battle.go 34 KB

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