service.go 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396
  1. package team
  2. import (
  3. capp "f1-game/internal/cherry_app"
  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. facade "f1-game/internal/facade"
  12. nameRoute "f1-game/internal/name/route"
  13. "f1-game/internal/pb"
  14. "f1-game/internal/sessions"
  15. "f1-game/internal/types"
  16. ao "f1-game/nodes/game/player/asset/origin"
  17. mapCall "f1-game/nodes/map/internal/call"
  18. "f1-game/nodes/map/internal/db"
  19. dbMapPlayer "f1-game/nodes/map/internal/db/data/player"
  20. mapFacade "f1-game/nodes/map/internal/facade"
  21. "f1-game/nodes/map/player/hero"
  22. ctime "github.com/cherry-game/cherry/extend/time"
  23. clog "github.com/cherry-game/cherry/logger"
  24. cproto "github.com/cherry-game/cherry/net/proto"
  25. )
  26. var srv = &service{}
  27. func Service() *service {
  28. return srv
  29. }
  30. type service struct {
  31. facade.PlayerServiceBase[*db.MapPlayerTable]
  32. }
  33. func (p *service) OnLogin(playerTable *db.MapPlayerTable, session *cproto.Session) {
  34. if session.Contains(sessions.IsNewPlayer) {
  35. clog.Infof("map team init: playerID = %d", playerTable.PlayerID)
  36. // 初始化队伍
  37. p.initTeams(playerTable)
  38. }
  39. // 计算城外征兵 TODO 优化成定时结算
  40. var (
  41. nowMillisecond = ctime.Now().ToMillisecond()
  42. diffSecond = (nowMillisecond - playerTable.Stats.LastLogoutTime) / ctime.MillisecondsPerSecond
  43. )
  44. if success := p.doTeamOutsideAutoTroops(playerTable, int32(diffSecond)); success {
  45. playerTable.SetChangeData()
  46. }
  47. if playerTable.IsChangeData() {
  48. playerTable.Save2Queue()
  49. }
  50. }
  51. func (p *service) initTeams(playerTable *db.MapPlayerTable) {
  52. // 检测未添加的队伍
  53. teamCount := playerTable.Facilities.Attrs.GetI32(enum.AttrTeamCount)
  54. for i := int32(1); i <= teamCount; i++ {
  55. _, success := playerTable.Teams.AddTeam(i)
  56. if success {
  57. playerTable.SetChangeData()
  58. }
  59. }
  60. if playerTable.IsChangeData() {
  61. playerTable.Save2Queue()
  62. }
  63. }
  64. // func (p *service) OnLogined(playerTable *db.MapPlayerTable, session *cproto.Session) {
  65. // }
  66. // func (p *service) OnLogout(playerTable *db.MapPlayerTable) {
  67. // }
  68. // func (p *service) OnReset(playerTable *db.MapPlayerTable, time *ctime.CherryTime, byLogin bool) {
  69. // }
  70. // Push 推送队伍信息
  71. func (p *service) Push(playerTable *db.MapPlayerTable, session *cproto.Session) {
  72. resp := playerTable.Teams.ToProto()
  73. sessions.PushWithSession(session, nameRoute.PushMapPlayer_Team, &resp)
  74. // 城外征兵推送
  75. p.TeamOutsideTroopsPush(playerTable.PlayerID)
  76. }
  77. // ChangePush 推送变化的队伍信息
  78. func (p *service) ChangePush(playerID int64, teams ...*dbMapPlayer.Team) {
  79. var (
  80. playerTable = db.GetMapPlayerTable(playerID)
  81. list []*pb.Team
  82. )
  83. // 检查玩家表是否存在
  84. if playerTable == nil {
  85. return
  86. }
  87. for _, team := range teams {
  88. proto := team.ToProto()
  89. list = append(list, &proto)
  90. }
  91. if len(list) == 0 {
  92. return
  93. }
  94. resp := &pb.Teams{
  95. List: list,
  96. }
  97. sessions.PushPlayer(playerID, nameRoute.PushMapPlayer_TeamChange, resp)
  98. }
  99. // 英雄状态更新推送
  100. func (p *service) HeroStateChangePush(playerID int64, updateHeroes ...*dbMapPlayer.Hero) {
  101. if len(updateHeroes) <= 0 {
  102. return
  103. }
  104. rsp := &pb.HeroStateResponse{}
  105. for _, hero := range updateHeroes {
  106. rsp.HeroState = append(rsp.HeroState, hero.ToHeroStateProto())
  107. }
  108. sessions.PushPlayer(playerID, nameRoute.PushMapPlayer_HeroState, rsp)
  109. }
  110. func (p *service) CheckEnergy(playerID int64, teamID int32, energy int32) int32 {
  111. playerTable := db.GetMapPlayerTable(playerID)
  112. if playerTable == nil {
  113. return code.PlayerNotFound
  114. }
  115. team, ok := playerTable.Teams.GetTeam(teamID)
  116. if !ok {
  117. return code.TeamNotUnlock
  118. }
  119. for _, member := range team.Members {
  120. hero, ok := playerTable.Heroes.Get(member.HeroGUID)
  121. if !ok {
  122. return code.HeroNotExist
  123. }
  124. if hero.Energy < energy {
  125. return code.HeroEnergyLack
  126. }
  127. }
  128. return code.OK
  129. }
  130. // SubEnergy 队伍扣除体力
  131. func (p *service) SubEnergy(playerID int64, teamID int32, energy int32) int32 {
  132. var (
  133. playerTable = db.GetMapPlayerTable(playerID)
  134. updateHeroes []*dbMapPlayer.Hero
  135. )
  136. // 检查玩家表是否存在
  137. if playerTable == nil {
  138. return code.PlayerNotFound
  139. }
  140. team, ok := playerTable.Teams.GetTeam(teamID)
  141. if !ok {
  142. return code.TeamNotUnlock
  143. }
  144. for _, member := range team.Members {
  145. dbHero, ok := playerTable.Heroes.Get(member.HeroGUID)
  146. if !ok {
  147. return code.HeroNotExist
  148. }
  149. if !hero.Service().SubEnergy(dbHero, energy) {
  150. return code.HeroEnergyLack
  151. }
  152. updateHeroes = append(updateHeroes, dbHero)
  153. }
  154. playerTable.Save2Queue()
  155. // 推送英雄数据
  156. hero.Service().ChangePush(playerID, updateHeroes...)
  157. return code.OK
  158. }
  159. // 回城自动补兵 return 是否补兵成功
  160. func (p *service) CheckAutoAddTeamTroops(playerID int64, teamID int32) bool {
  161. if teamID <= 0 {
  162. return false
  163. }
  164. playerTable := db.GetMapPlayerTable(playerID)
  165. if playerTable == nil {
  166. clog.Warnf("[CheckAutoAddTeamTroops] player not found, playerID: %d", playerID)
  167. return false
  168. }
  169. team, ok := playerTable.Teams.GetTeam(teamID)
  170. if !ok {
  171. clog.Warnf("[CheckAutoAddTeamTroops] team not found, teamID: %d", teamID)
  172. return false
  173. }
  174. // 没设置自动补兵 跳过
  175. if !team.AutoAddTroops {
  176. return false
  177. }
  178. var (
  179. // 最终分兵数量
  180. finalMemberTroops = map[int32]*pb.I32I32{}
  181. // 英雄带兵上限 key 英雄上阵阵容位置
  182. heroArmsLimitMap = map[int32]int32{}
  183. // 通过士兵等级分组
  184. troopsIDGroupMemberMap = team.GetTroopsIDGroupMemberMap()
  185. costAssets = types.Assets{}
  186. )
  187. for troopsID, members := range troopsIDGroupMemberMap {
  188. // 可分配的兵力
  189. hasTroopsNum := int32(mapFacade.Storage.Get(playerID, troopsID))
  190. if hasTroopsNum <= 0 {
  191. continue
  192. }
  193. needCostCount := int32(0)
  194. for _, member := range members {
  195. heroEntity, found := playerTable.Heroes.Get(member.HeroGUID)
  196. if !found {
  197. // 英雄不存在 整组失败
  198. needCostCount = 0
  199. break
  200. }
  201. heroArmsLimit := p.GetHeroTroopsNum(playerID, heroEntity.HeroID, member.TeamIndex)
  202. // 当前需要补兵数量 = 带兵上限 - 当前剩余兵力 - 伤兵数
  203. needCostCount += heroArmsLimit - member.TroopsNum - member.InjureTroops
  204. heroArmsLimitMap[member.TeamIndex] = heroArmsLimit
  205. }
  206. // 不需要补兵.0
  207. if needCostCount <= 0 {
  208. continue
  209. }
  210. if needCostCount <= hasTroopsNum {
  211. // 全部补满
  212. for _, member := range members {
  213. data := pb.I32I32{
  214. Key: troopsID,
  215. Value: heroArmsLimitMap[member.TeamIndex] - member.TroopsNum - member.InjureTroops,
  216. }
  217. finalMemberTroops[member.TeamIndex] = &data
  218. }
  219. } else {
  220. // 优先持平补兵
  221. needCostCount = p.balanceTroopsDistribution(members, troopsID, heroArmsLimitMap, hasTroopsNum, finalMemberTroops)
  222. if needCostCount <= 0 {
  223. continue
  224. }
  225. }
  226. costAssets.Add(troopsID, int64(needCostCount))
  227. }
  228. if len(costAssets) <= 0 {
  229. return false
  230. }
  231. // 扣除兵力
  232. _, errCode := mapFacade.Storage.AssetSubs(playerID, costAssets, ao.TeamAutoTroopsCost)
  233. if code.IsFail(errCode) {
  234. clog.Warnf("[AutoAddTeamTroops] sub troop fail, playerID: %d, teamID: %d, errCode: %d", playerID, teamID, errCode)
  235. return false
  236. }
  237. // 更新队伍上阵英雄带兵数量
  238. team.UpdateTeamMemberTroopsNum(finalMemberTroops)
  239. // 同步到 redis 队伍信息
  240. p.UpdatePlayerTeams(playerID, team)
  241. // 发送分兵事件
  242. capp.PostEvent(event.NewTeamTroops(playerID, teamID))
  243. return true
  244. }
  245. // 持平补兵分配算法
  246. func (p *service) balanceTroopsDistribution(members []*dbMapPlayer.TeamMember, troopsID int32, heroArmsLimitMap map[int32]int32, hasTroopsNum int32, finalMemberTroops map[int32]*pb.I32I32) int32 {
  247. if len(members) == 0 || hasTroopsNum <= 0 {
  248. return 0
  249. }
  250. // 初始化当前兵力状态
  251. currentTroops := make(map[int32]int32)
  252. for _, member := range members {
  253. currentTroops[member.TeamIndex] = member.TroopsNum + member.InjureTroops
  254. finalMemberTroops[member.TeamIndex] = &pb.I32I32{Key: troopsID, Value: 0} // 初始化分配结果
  255. }
  256. var (
  257. intMaxValue = int32(1<<31 - 1)
  258. remaining = hasTroopsNum
  259. totalAdded = int32(0)
  260. )
  261. for remaining > 0 {
  262. // 找出所有未达到上限的英雄
  263. var availableHeroes []int32
  264. for _, member := range members {
  265. if currentTroops[member.TeamIndex] < heroArmsLimitMap[member.TeamIndex] {
  266. availableHeroes = append(availableHeroes, member.TeamIndex)
  267. }
  268. }
  269. if len(availableHeroes) == 0 {
  270. break // 所有英雄都已满
  271. }
  272. // 找出当前兵力最少的英雄们
  273. minTroops := intMaxValue
  274. var minHeroes []int32
  275. for _, teamIndex := range availableHeroes {
  276. if currentTroops[teamIndex] < minTroops {
  277. minTroops = currentTroops[teamIndex]
  278. minHeroes = []int32{teamIndex}
  279. } else if currentTroops[teamIndex] == minTroops {
  280. minHeroes = append(minHeroes, teamIndex)
  281. }
  282. }
  283. // 找出下一个更高的兵力水平(在可用英雄中)
  284. nextLevel := intMaxValue
  285. for _, teamIndex := range availableHeroes {
  286. if currentTroops[teamIndex] > minTroops && currentTroops[teamIndex] < nextLevel {
  287. nextLevel = currentTroops[teamIndex]
  288. }
  289. }
  290. // 如果没有找到更高的水平,使用最小上限值
  291. if nextLevel == intMaxValue {
  292. for _, teamIndex := range minHeroes {
  293. nextLevel = math.Min(nextLevel, heroArmsLimitMap[teamIndex])
  294. }
  295. }
  296. // 计算需要分配到下一个水平的总兵力
  297. totalNeeded := int32(0)
  298. for _, teamIndex := range minHeroes {
  299. needed := math.Min(nextLevel, heroArmsLimitMap[teamIndex]) - currentTroops[teamIndex]
  300. if needed > 0 {
  301. totalNeeded += needed
  302. }
  303. }
  304. if totalNeeded <= remaining {
  305. // 有足够兵力让所有最低英雄达到下一个水平
  306. for _, teamIndex := range minHeroes {
  307. addAmount := math.Min(nextLevel, heroArmsLimitMap[teamIndex]) - currentTroops[teamIndex]
  308. currentTroops[teamIndex] += addAmount
  309. finalMemberTroops[teamIndex].Value += addAmount
  310. totalAdded += addAmount
  311. remaining -= addAmount
  312. }
  313. } else {
  314. // 兵力不足,需要平均分配
  315. numHeroes := int32(len(minHeroes))
  316. baseAdd := remaining / numHeroes
  317. remainder := remaining % numHeroes
  318. for i, teamIndex := range minHeroes {
  319. addAmount := baseAdd
  320. if int32(i) < remainder {
  321. addAmount++
  322. }
  323. // 确保不超过上限
  324. maxPossibleAdd := heroArmsLimitMap[teamIndex] - currentTroops[teamIndex]
  325. if addAmount > maxPossibleAdd {
  326. addAmount = maxPossibleAdd
  327. }
  328. if addAmount > remaining {
  329. addAmount = remaining
  330. }
  331. currentTroops[teamIndex] += addAmount
  332. finalMemberTroops[teamIndex].Value += addAmount
  333. totalAdded += addAmount
  334. remaining -= addAmount
  335. if remaining <= 0 {
  336. break
  337. }
  338. }
  339. break
  340. }
  341. }
  342. return totalAdded
  343. }
  344. // 同步队伍信息到 redis
  345. func (p *service) UpdatePlayerTeams(playerID int64, teamList ...*dbMapPlayer.Team) {
  346. if len(teamList) == 0 {
  347. return
  348. }
  349. var battleTeamList []*pb.BattleTeam
  350. for _, team := range teamList {
  351. proto := team.ToBattleTeamProto()
  352. battleTeamList = append(battleTeamList, &proto)
  353. }
  354. redis.Game.UpdatePlayerTeams(playerID, battleTeamList...)
  355. }
  356. // 通过英雄列表获取羁绊列表
  357. func (p *service) GetFetterListByHeroList(playerID int64, heroIDList types.Set[int32]) types.Set[int32] {
  358. if len(heroIDList) < 1 {
  359. return nil
  360. }
  361. activeFetterIDs := types.Set[int32]{}
  362. for heroID := range heroIDList {
  363. fetterIDs := data.HeroFetter.GetFetterIDs(heroID)
  364. // 当前英雄没有羁绊,则跳过
  365. if len(fetterIDs) < 1 {
  366. continue
  367. }
  368. for fetterID := range fetterIDs {
  369. if activeFetterIDs.Contains(fetterID) {
  370. continue
  371. }
  372. fetterRow, found := data.HeroFetter.GetByID(fetterID)
  373. if !found {
  374. clog.Warnf("[GetFetterListByHeroList] fetter not found, playerID:%d, fetterID: %d", playerID, fetterID)
  375. continue
  376. }
  377. curNum := int32(0)
  378. for _, needHeroID := range fetterRow.HeroIDs {
  379. if heroIDList.Contains(needHeroID) {
  380. curNum++
  381. // 如果当前所需英雄数量已经满足,则直接添加羁绊ID,并跳过
  382. if curNum >= fetterRow.ActiveHeroNum {
  383. activeFetterIDs.Add(fetterID)
  384. break
  385. }
  386. }
  387. }
  388. }
  389. }
  390. return activeFetterIDs
  391. }
  392. // 获取英雄带兵上限
  393. func (p *service) GetHeroTroopsNum(playerID int64, heroID, teamIndex int32) int32 {
  394. playerTable := db.GetMapPlayerTable(playerID)
  395. if playerTable == nil {
  396. clog.Warnf("[GetHeroTroopsNum] player not found, playerID: %d", playerID)
  397. return 0
  398. }
  399. return p.doGetHeroTroopsNum(playerTable, heroID, teamIndex)
  400. }
  401. // 获取英雄带兵上限
  402. func (p *service) doGetHeroTroopsNum(playerTable *db.MapPlayerTable, heroID, teamIndex int32) int32 {
  403. var heroTroopsLimit int32
  404. // 1.英雄初始统兵数量
  405. heroRow, found := data.Hero.GetByID(heroID)
  406. if found {
  407. heroTroopsLimit += heroRow.InitTroop
  408. } else {
  409. clog.Warnf("[GetHeroTroopsNum] hero config not found, heroID: %d", heroID)
  410. }
  411. // 2.判断是否为主将 主将额外增加带兵数
  412. if constant.IsTeamMainIndex(teamIndex) {
  413. heroTroopsLimit += data.Const.TeamMainAddTroops
  414. }
  415. // 3.添加城建增加的带兵数
  416. heroTroopsLimit += int32(playerTable.Facilities.Attrs.Get(enum.AttrTeamArms))
  417. return heroTroopsLimit
  418. }
  419. // 部队羁绊加成
  420. func (p *service) TeamFetterAttrs(team *dbMapPlayer.Team, heroes dbMapPlayer.Heroes) {
  421. fetterIDs := team.FetterIDs
  422. if fetterIDs.Size() < 1 {
  423. return
  424. }
  425. for fetterID := range fetterIDs {
  426. for _, member := range team.Members {
  427. hero, found := heroes.Get(member.HeroGUID)
  428. if !found {
  429. continue
  430. }
  431. attrs := types.NewAttrs()
  432. fetterRow, found := data.HeroFetter.GetByID(fetterID)
  433. if !found {
  434. clog.Warnf("[teamFetterAttrs] fetter not found, heroID:%d, fetterID: %d", hero.HeroID, fetterID)
  435. continue
  436. }
  437. // 当前羁绊不存在该英雄,则跳过
  438. if !fetterRow.HeroIDs.Contains(hero.HeroID) {
  439. continue
  440. }
  441. for attrId, attrValue := range fetterRow.GetAttrs() {
  442. attrs.Add(attrId, attrValue)
  443. }
  444. member.FetterAttrs = attrs
  445. }
  446. }
  447. }
  448. // 更新队伍状态
  449. func (p *service) UpdateTeamStateByMarchState(playerTable *db.MapPlayerTable, team *dbMapPlayer.Team, marchState enum.MarchState) {
  450. teamState := enum.MarchStateToTeamState(marchState)
  451. p.UpdateTeamState(playerTable, team, teamState)
  452. }
  453. // 更新队伍状态
  454. func (p *service) UpdateTeamState(playerTable *db.MapPlayerTable, team *dbMapPlayer.Team, teamState enum.TeamState) {
  455. // 相同状态不处理
  456. if team.State == teamState {
  457. return
  458. }
  459. team.State = teamState
  460. // 回城待命
  461. if teamState == enum.TeamState_Standby {
  462. // 检测自动补兵
  463. p.CheckAutoAddTeamTroops(playerTable.PlayerID, team.TeamID)
  464. }
  465. playerTable.Save2Queue()
  466. clog.Debugf("[UpdateTeamState] PlayerID = %d, TeamID = %d, TeamState = %s", playerTable.PlayerID, team.TeamID, enum.GetTeamStateName(teamState))
  467. // 推送状态给客户端
  468. p.ChangePush(playerTable.PlayerID, team)
  469. }
  470. // 获取队伍行军同步数据
  471. func (p *service) GetTeamMarchSyncProto(playerTable *db.MapPlayerTable, dbTeam *dbMapPlayer.Team) *pb.MarchSync {
  472. var (
  473. members []*pb.MarchMember
  474. injureEndTime int64
  475. )
  476. for _, member := range dbTeam.Members {
  477. // 士兵负重
  478. var armWeight int64
  479. if armTypeRow, ok := data.ArmType.GetByID(member.TroopsID); ok {
  480. armWeight = armTypeRow.Weight
  481. }
  482. heroEntity, _ := playerTable.Heroes.Get(member.HeroGUID)
  483. // 获取带兵上限
  484. member.TroopsLimit = p.doGetHeroTroopsNum(playerTable, heroEntity.HeroID, member.TeamIndex)
  485. members = append(members, &pb.MarchMember{
  486. TeamIndex: member.TeamIndex,
  487. LineupIndex: member.Index,
  488. HeroGUID: heroEntity.GUID,
  489. HeroConfigID: heroEntity.HeroID,
  490. Energy: heroEntity.Energy,
  491. ArmID: member.TroopTypeID,
  492. ArmWeight: armWeight,
  493. TroopsMax: member.TroopsLimit,
  494. TroopsNum: member.TroopsNum,
  495. })
  496. injureEndTime = max(injureEndTime, heroEntity.InjuriesEndTime)
  497. }
  498. req := &pb.MarchSync{
  499. PlayerID: playerTable.PlayerID,
  500. LeagueID: playerTable.LeagueID,
  501. TeamID: dbTeam.TeamID,
  502. ObjectID: dbTeam.MarchObjectID,
  503. Point: playerTable.Point.ToProto(),
  504. Members: members,
  505. GameNodeID: playerTable.GameNodeID,
  506. InjureEndTime: injureEndTime * ctime.MillisecondsPerSecond,
  507. }
  508. // 出征行军更新玩家队伍兵力上限
  509. // TODO 这个需要拿出去不
  510. p.UpdatePlayerTeams(playerTable.PlayerID, dbTeam)
  511. return req
  512. }
  513. // 检查行军消耗是否足够
  514. func (p *service) CheckMarchCost(playerID int64, teamID int32, marchType enum.MarchType, pathLen int) int32 {
  515. row, ok := data.MapMarch.GetByMarchType(marchType)
  516. if !ok {
  517. return code.ConfigNotFound_MapMarch
  518. }
  519. // 检查体力是否足够
  520. energyCost := row.GetEnergyCost(pathLen)
  521. if row.EnergyCost > 0 {
  522. errCode := p.CheckEnergy(playerID, teamID, energyCost)
  523. if code.IsFail(errCode) {
  524. return errCode
  525. }
  526. }
  527. // 检查扣除资源
  528. if !row.Cost.IsEmpty() {
  529. if !mapFacade.Storage.IsAllEnough(playerID, row.Cost) {
  530. return code.ItemNotEnough
  531. }
  532. }
  533. return code.OK
  534. }
  535. // 行军消耗资源
  536. func (p *service) MarchCost(playerID int64, teamID int32, marchType enum.MarchType, pathLen int) int32 {
  537. row, ok := data.MapMarch.GetByMarchType(marchType)
  538. if !ok {
  539. return code.ConfigNotFound_MapMarch
  540. }
  541. // 扣除资源
  542. if !row.Cost.IsEmpty() {
  543. _, errCode := mapFacade.Storage.AssetSubs(playerID, row.Cost, ao.None, true)
  544. if code.IsFail(errCode) {
  545. return errCode
  546. }
  547. clog.Debugf("[marchCost] cost: PlayerID: %d, teamID: %d, cost: %v",
  548. playerID, teamID, row.Cost)
  549. }
  550. energyCost := row.GetEnergyCost(pathLen)
  551. // 扣体力
  552. if row.EnergyCost > 0 {
  553. p.SubEnergy(playerID, teamID, energyCost)
  554. clog.Debugf("[marchCost] sub energy: PlayerID: %d, teamID: %d, energy: %d",
  555. playerID, teamID, energyCost)
  556. }
  557. return code.OK
  558. }
  559. // 执行城外征兵逻辑
  560. func (p *service) DoTeamOutsideTroops(playerID int64, team *dbMapPlayer.Team, troopNums []*pb.I32I32) int32 {
  561. if len(troopNums) <= 0 {
  562. return code.IllegalArgument
  563. }
  564. var (
  565. // 检测征兵数量+当前兵力是否超出上限 & 获取征兵消耗
  566. costs = types.Assets{}
  567. playerTable = db.GetMapPlayerTable(playerID)
  568. )
  569. for _, troopsNum := range troopNums {
  570. // 征兵数量错误
  571. if troopsNum.Value <= 0 {
  572. return code.TeamTroopsNumError
  573. }
  574. teamMember, found := team.GetTeamMember(troopsNum.Key)
  575. if !found {
  576. return code.TeamMemberNotExist
  577. }
  578. // 判断当前是否已在城外征兵
  579. if teamMember.IsOutsideTroopsing() {
  580. return code.TeamOutsideConscripting
  581. }
  582. heroEntity, found := playerTable.Heroes.Get(teamMember.HeroGUID)
  583. if !found {
  584. return code.HeroNotExist
  585. }
  586. troopsLimit := p.GetHeroTroopsNum(playerID, heroEntity.HeroID, troopsNum.Key)
  587. // 兵力数量超出上限
  588. if troopsNum.Value+teamMember.TroopsNum > troopsLimit {
  589. return code.TeamArmExceed
  590. }
  591. armLevelRow, found := data.ArmLevel.GetByID(teamMember.TroopsID)
  592. if !found {
  593. return code.ConfigNotExist_Conscript
  594. }
  595. singleCosts := armLevelRow.CalOutConscriptAssets(int64(troopsNum.Value))
  596. costs.AddAssets(singleCosts)
  597. }
  598. // 消耗道具
  599. _, errCode := mapFacade.Storage.AssetSubs(playerID, costs, ao.ConscriptOutsideCost, true)
  600. if code.IsFail(errCode) {
  601. return errCode
  602. }
  603. curTime := ctime.Now().ToSecond()
  604. for _, troopsNum := range troopNums {
  605. teamMember, _ := team.GetTeamMember(troopsNum.Key)
  606. teamMember.OutsideConscript.TroopsCount = 0
  607. teamMember.OutsideConscript.TotalTroopsCount = troopsNum.Value
  608. // 计算耗时
  609. armLevelRow, _ := data.ArmLevel.GetByID(teamMember.TroopsID)
  610. second := troopsNum.Value / armLevelRow.ConscriptNumSecond
  611. if troopsNum.Value%armLevelRow.ConscriptNumSecond > 0 {
  612. second++
  613. }
  614. // 计算结束时间戳
  615. teamMember.OutsideConscript.EndTime = curTime + int64(second)
  616. }
  617. // 通知地图自动征兵OK
  618. mapCall.Logic.AutoOutsideTroopsNotify(playerTable.PlayerID, team.TeamID, team.GetOutsideConscriptMaxTime())
  619. return code.OK
  620. }
  621. // 定时检测城外征兵
  622. func (p *service) CheckCanOutsideAutoTroopsTimer(playerID int64, nowSecond int64) {
  623. // 没到检测时间
  624. if nowSecond%data.Const.MapTroopsCheckInterval != 0 {
  625. return
  626. }
  627. playerTable := db.GetMapPlayerTable(playerID)
  628. if playerTable == nil {
  629. clog.Warnf("[CheckCanOutsideAutoTroopsTimer] player not found, playerID: %d", playerID)
  630. return
  631. }
  632. save := false
  633. for _, team := range playerTable.Teams {
  634. if team.IsCanOutsideAutoTroops() {
  635. // 执行城外征兵
  636. if ok := p.OutsideAutoTroops(playerTable, team); ok {
  637. save = true
  638. }
  639. }
  640. }
  641. if save {
  642. playerTable.Save2Queue()
  643. }
  644. }
  645. // 城外自动征兵定时器 //TODO 暂定每秒执行
  646. func (p *service) TeamOutsideAutoTroopsTimer(playerID int64, nowSecond int64) {
  647. playerTable := db.GetMapPlayerTable(playerID)
  648. if playerTable == nil {
  649. clog.Warnf("[TeamOutsideAutoTroopsTimer] player not found, playerID: %d", playerID)
  650. return
  651. }
  652. if save := p.doTeamOutsideAutoTroops(playerTable, 1); save {
  653. playerTable.Save2Queue()
  654. }
  655. }
  656. func (p *service) doTeamOutsideAutoTroops(playerTable *db.MapPlayerTable, seconds int32) bool {
  657. if seconds <= 0 {
  658. return false
  659. }
  660. updateTeams := []*dbMapPlayer.Team{}
  661. for _, team := range playerTable.Teams {
  662. update := false
  663. for _, member := range team.Members {
  664. if member.TroopsID <= 0 {
  665. continue
  666. }
  667. remainCount := member.GetRemainTroopsCount()
  668. // 城外征兵已完成
  669. if remainCount <= 0 {
  670. continue
  671. }
  672. armLevelRow, found := data.ArmLevel.GetByID(member.TroopsID)
  673. if !found {
  674. clog.Warnf("[doTeamOutsideAutoTroops] armLevelRow not found, playerID: %d, troopsID: %d", playerTable.PlayerID, member.TroopsID)
  675. continue
  676. }
  677. getCount := seconds * armLevelRow.ConscriptNumSecond
  678. if getCount > remainCount {
  679. getCount = remainCount
  680. }
  681. heroEntity, found := playerTable.Heroes.Get(member.HeroGUID)
  682. if !found {
  683. clog.Warnf("[doTeamOutsideAutoTroops] heroEntity not found, playerID: %d, heroGUID: %d", playerTable.PlayerID, member.HeroGUID)
  684. continue
  685. }
  686. troopsLimit := p.GetHeroTroopsNum(playerTable.PlayerID, heroEntity.HeroID, member.TeamIndex)
  687. if member.TroopsNum+getCount > troopsLimit {
  688. getCount = troopsLimit - member.TroopsNum
  689. }
  690. if getCount <= 0 {
  691. continue
  692. }
  693. member.OutsideConscript.TroopsCount += getCount
  694. member.TroopsNum += getCount
  695. update = true
  696. }
  697. if update {
  698. // 队伍城外征兵已完成 重置征兵数据
  699. if team.IsTeamOutsideTroopsFinish() {
  700. for _, member := range team.Members {
  701. member.ResetOutsideTroops()
  702. }
  703. }
  704. updateTeams = append(updateTeams, team)
  705. }
  706. }
  707. if len(updateTeams) > 0 {
  708. // 推送城外征兵数据 TODO 是否合并到队伍信息中?
  709. p.TeamOutsideTroopsChangePush(playerTable.PlayerID, updateTeams...)
  710. // 推送队伍兵力变更
  711. p.UpdatePlayerTeams(playerTable.PlayerID, updateTeams...)
  712. // 推送队伍兵力
  713. p.ChangePush(playerTable.PlayerID, updateTeams...)
  714. return true
  715. }
  716. return false
  717. }
  718. // 城外征兵推送
  719. func (p *service) TeamOutsideTroopsPush(playerID int64) {
  720. teamPush := &pb.TeamOutsideTroopsList{}
  721. playerTable := db.GetMapPlayerTable(playerID)
  722. for _, team := range playerTable.Teams {
  723. teamPush.List = append(teamPush.List, team.ToOutsideTroopsProto())
  724. }
  725. if len(teamPush.List) > 0 {
  726. sessions.PushPlayer(playerID, nameRoute.PushMapPlayer_TeamOutsideTroops, teamPush)
  727. }
  728. }
  729. // 城外征兵推送
  730. func (p *service) TeamOutsideTroopsChangePush(playerID int64, teams ...*dbMapPlayer.Team) {
  731. if len(teams) <= 0 {
  732. return
  733. }
  734. teamPush := &pb.TeamOutsideTroopsList{}
  735. for _, team := range teams {
  736. teamPush.List = append(teamPush.List, team.ToOutsideTroopsProto())
  737. }
  738. if len(teamPush.List) > 0 {
  739. sessions.PushPlayer(playerID, nameRoute.PushMapPlayer_TeamOutsideTroops, teamPush)
  740. }
  741. }
  742. // 取消城外征兵
  743. func (p *service) TeamOutsideTroopsCancel(playerTable *db.MapPlayerTable, team *dbMapPlayer.Team, teamIndexs []int32) int32 {
  744. var (
  745. returnCosts = types.Assets{}
  746. playerID = playerTable.PlayerID
  747. memberCancels = []*dbMapPlayer.TeamMember{}
  748. )
  749. for _, teamIndex := range teamIndexs {
  750. teamMember, found := team.GetTeamMember(teamIndex)
  751. if !found {
  752. continue
  753. }
  754. // 当前没有城外征兵
  755. if teamMember.OutsideConscript.TroopsCount <= 0 {
  756. continue
  757. }
  758. // 存在没征完的士兵 返还奖励
  759. remainCount := teamMember.GetRemainTroopsCount()
  760. if remainCount > 0 {
  761. armLevelRow, found := data.ArmLevel.GetByID(teamMember.TroopsID)
  762. if !found {
  763. continue
  764. }
  765. singleCosts := armLevelRow.CalOutConscriptAssets(int64(remainCount))
  766. returnCosts.AddAssets(singleCosts)
  767. }
  768. memberCancels = append(memberCancels, teamMember)
  769. }
  770. // 没有任何返还 说明也没有城外征兵/城外征兵已完成
  771. if len(returnCosts) > 0 {
  772. _, errCode := mapFacade.Storage.AssetAdds(playerID, returnCosts, ao.ConscriptOutsideReturn, true)
  773. if code.IsFail(errCode) {
  774. return errCode
  775. }
  776. }
  777. // 重置需要需要的征兵数据
  778. for _, member := range memberCancels {
  779. member.ResetOutsideTroops()
  780. }
  781. // 通知地图自动征兵OK
  782. mapCall.Logic.AutoOutsideTroopsNotify(playerTable.PlayerID, team.TeamID, team.GetOutsideConscriptMaxTime())
  783. return code.OK
  784. }
  785. func (p *service) OutsideAutoTroops(playerTable *db.MapPlayerTable, team *dbMapPlayer.Team) bool {
  786. if !team.IsAutoTroops {
  787. return false
  788. }
  789. // 计算所需征兵数量&各成员所需征兵数量
  790. var (
  791. teamMembers = map[int32]*pb.TeamOutsideTroopsMember{}
  792. oneTroopCosts = map[int32]types.Assets{}
  793. curHasAssets = map[int32]int64{}
  794. )
  795. for _, member := range team.Members {
  796. // 过滤没有士兵等级ID的成员
  797. if member.TroopsID <= 0 {
  798. continue
  799. }
  800. armLevelRow, found := data.ArmLevel.GetByID(member.TroopsID)
  801. if !found {
  802. clog.Warnf("[outsideAutoTroops] armLevel not found. playerID:%d, teamID: %d, troopsID: %d", playerTable.PlayerID, team.TeamID, member.TroopsID)
  803. continue
  804. }
  805. heroEntity, found := playerTable.Heroes.Get(member.HeroGUID)
  806. if !found {
  807. clog.Warnf("[outsideAutoTroops] hero not found. playerID:%d, teamID: %d, heroGUID: %d", playerTable.PlayerID, team.TeamID, member.HeroGUID)
  808. continue
  809. }
  810. troopsLimit := p.GetHeroTroopsNum(playerTable.PlayerID, heroEntity.HeroID, member.Index)
  811. needTroops := troopsLimit - member.TroopsNum
  812. if needTroops <= 0 {
  813. continue
  814. }
  815. teamMembers[member.TeamIndex] = &pb.TeamOutsideTroopsMember{
  816. TroopsID: member.TroopsID,
  817. TroopsNum: member.TroopsNum,
  818. TroopsLimit: troopsLimit,
  819. }
  820. // 获取征一个士兵的消耗
  821. _, found = oneTroopCosts[member.TroopsID]
  822. if !found {
  823. costs := armLevelRow.CalOutConscriptAssets(1)
  824. oneTroopCosts[member.TroopsID] = costs
  825. // 获取当前道具的数量
  826. for _, cost := range costs {
  827. _, found := curHasAssets[cost.ID]
  828. if !found {
  829. hasNum := mapFacade.Storage.Get(playerTable.PlayerID, cost.ID)
  830. // TODO 如果数量<=0 是不是当前征兵失败
  831. curHasAssets[cost.ID] = hasNum
  832. }
  833. }
  834. }
  835. }
  836. // 如果当前没有征兵需求
  837. if len(teamMembers) <= 0 {
  838. return false
  839. }
  840. // 如果征兵数量>0 则获取每个成员最大征兵上限
  841. finalMemberTroops := p.HandleAutoTroops(curHasAssets, oneTroopCosts, teamMembers)
  842. if len(finalMemberTroops) <= 0 {
  843. clog.Warnf("[outsideAutoTroops] handle auto troops fail. playerID:%d, teamID:%d", playerTable.PlayerID, team.TeamID)
  844. return false
  845. }
  846. // 调用城外征兵核心逻辑
  847. errCode := p.DoTeamOutsideTroops(playerTable.PlayerID, team, finalMemberTroops)
  848. if code.IsFail(errCode) {
  849. clog.Warnf("[outsideAutoTroops] fail. playerID:%d, teamID: %d, errCode: %d", playerTable.PlayerID, team.TeamID, errCode)
  850. return false
  851. }
  852. // 同步给客户端
  853. p.TeamOutsideTroopsChangePush(playerTable.PlayerID, team)
  854. return true
  855. }
  856. // 规则:优先拉平队伍每个成员兵力(即优先给最小兵力的成员征兵),如果成员当前兵力相同后,则同步分兵
  857. // HandleAutoTroops 优化后的征兵逻辑 // TODO 后面看继续优化当前逻辑
  858. // curHasAssets: 当前库存
  859. // troopCosts: 兵种消耗配置 [兵种ID]消耗材料
  860. // teamMembers: [位置]部队成员信息(作为源数据查询,不直接修改)
  861. func (p *service) HandleAutoTroops(curHasAssets map[int32]int64, troopCosts map[int32]types.Assets,
  862. teamMembers map[int32]*pb.TeamOutsideTroopsMember) []*pb.I32I32 {
  863. // 初始化结果
  864. var (
  865. finalMemberTroops = map[int32]int32{} // [位置]征兵数量
  866. maxInt32 = int32(1<<31 - 1)
  867. maxInt64 = int64(1<<63 - 1)
  868. )
  869. // 追踪每个位置的当前兵力
  870. curTroops := make(map[int32]int32)
  871. for position, hero := range teamMembers {
  872. curTroops[position] = hero.TroopsNum
  873. }
  874. // 持续循环征兵,直到所有需求满足或资源耗尽
  875. for {
  876. // 找出当前最少兵力的英雄(且需要征兵)
  877. minTroops := maxInt32
  878. for _, troops := range curTroops {
  879. if troops < minTroops {
  880. minTroops = troops
  881. }
  882. }
  883. if minTroops == maxInt32 {
  884. break
  885. }
  886. // 找出所有兵力等于最少且需要征兵的位置
  887. // (curTroops中只保留未达到上限的位置)
  888. var canRecruitPositions []int32
  889. for position, troops := range curTroops {
  890. if troops == minTroops {
  891. canRecruitPositions = append(canRecruitPositions, position)
  892. }
  893. }
  894. if len(canRecruitPositions) == 0 {
  895. break
  896. }
  897. // 计算本轮所有英雄同步征兵的总材料消耗(每个英雄征1个兵)
  898. materialPerRound := map[int32]int64{}
  899. for _, position := range canRecruitPositions {
  900. troopID := teamMembers[position].TroopsID
  901. costs := troopCosts[troopID]
  902. for _, asset := range costs {
  903. materialPerRound[asset.ID] += asset.Num
  904. }
  905. }
  906. // 计算本轮最多能同步征兵多少次
  907. maxRound := maxInt64
  908. for itemID, cost := range materialPerRound {
  909. if cost == 0 {
  910. continue
  911. }
  912. stock := curHasAssets[itemID]
  913. if stock < cost {
  914. maxRound = 0
  915. break
  916. }
  917. if rounds := stock / cost; rounds < maxRound {
  918. maxRound = rounds
  919. }
  920. }
  921. if maxRound <= 0 {
  922. break
  923. }
  924. // 找出本轮英雄中剩余需求最小的
  925. minNeed := maxInt32
  926. for _, position := range canRecruitPositions {
  927. need := teamMembers[position].TroopsLimit - curTroops[position]
  928. if need < minNeed {
  929. minNeed = need
  930. }
  931. }
  932. // 找出下一个更高兵力的等级(用于拉平计算)
  933. // (curTroops中只保留未达到上限的位置)
  934. nextLevelTroops := maxInt32
  935. for _, troops := range curTroops {
  936. if troops > minTroops && troops < nextLevelTroops {
  937. nextLevelTroops = troops
  938. }
  939. }
  940. // 确定本轮征兵数量:取minNeed和(nextLevelTroops-minTroops)的较小值
  941. roundRecruit := int32(maxRound)
  942. if maxRound > maxInt64 {
  943. roundRecruit = maxInt32
  944. }
  945. if roundRecruit > minNeed {
  946. roundRecruit = minNeed
  947. }
  948. // 优化:当没有下一级兵力(所有可征兵位置兵力相同),不限制拉平
  949. // 这样可以一次性征到资源耗尽或征满,而不是每次只征1个
  950. if nextLevelTroops != maxInt32 {
  951. maxToLevel := nextLevelTroops - minTroops
  952. if roundRecruit > maxToLevel {
  953. roundRecruit = maxToLevel
  954. }
  955. }
  956. // 本轮执行征兵
  957. recruited := false
  958. for _, position := range canRecruitPositions {
  959. need := teamMembers[position].TroopsLimit - curTroops[position]
  960. if need <= 0 {
  961. continue
  962. }
  963. troopID := teamMembers[position].TroopsID
  964. costs, ok := troopCosts[troopID]
  965. if !ok || len(costs) == 0 {
  966. continue
  967. }
  968. // 确定实际征兵量
  969. actualRecruit := roundRecruit
  970. if need < actualRecruit {
  971. actualRecruit = need
  972. }
  973. // 更新征兵数据
  974. finalMemberTroops[position] += actualRecruit
  975. for _, asset := range costs {
  976. costAmount := int64(actualRecruit) * asset.Num
  977. curHasAssets[asset.ID] -= costAmount
  978. }
  979. // 更新当前兵力
  980. curTroops[position] += actualRecruit
  981. // 如果达到上限,从curTroops中删除该位置
  982. if teamMembers[position].TroopsLimit-curTroops[position] <= 0 {
  983. delete(curTroops, position)
  984. }
  985. if actualRecruit > 0 {
  986. recruited = true
  987. }
  988. }
  989. // 本轮无征兵时退出循环
  990. if !recruited {
  991. break
  992. }
  993. }
  994. result := []*pb.I32I32{}
  995. for position, value := range finalMemberTroops {
  996. result = append(result, &pb.I32I32{
  997. Key: position,
  998. Value: value,
  999. })
  1000. }
  1001. return result
  1002. }
  1003. // 行军至(回城\调度点\营帐)恢复伤兵,战败重伤的在重伤的逻辑恢复
  1004. func (p *service) RecoveryInjureTroops(playerTable *db.MapPlayerTable, team *dbMapPlayer.Team) {
  1005. var marchTroops []*pb.MarchTroops
  1006. for _, member := range team.Members {
  1007. if member.InjureTroops <= 0 {
  1008. continue
  1009. }
  1010. member.TroopsNum += member.InjureTroops
  1011. marchTroops = append(marchTroops, &pb.MarchTroops{
  1012. TeamIndex: member.TeamIndex,
  1013. TroopsNum: member.InjureTroops,
  1014. })
  1015. // 重置伤兵数
  1016. member.InjureTroops = 0
  1017. }
  1018. // 没有需要恢复的伤兵
  1019. if len(marchTroops) <= 0 {
  1020. return
  1021. }
  1022. playerTable.Save2Queue()
  1023. // 更新地图队伍兵力数据
  1024. mapCall.Logic.MarchSyncTroops(team.TeamID, team.MarchObjectID, marchTroops)
  1025. // 同步队伍变更信息
  1026. p.ChangePush(playerTable.PlayerID, team)
  1027. // 兵力同步到redis
  1028. p.UpdatePlayerTeams(playerTable.PlayerID, team)
  1029. }
  1030. // 刷新队伍阵容位置
  1031. func (p *service) RefreshTeamLineup(playerTable *db.MapPlayerTable, team *dbMapPlayer.Team) {
  1032. if team == nil {
  1033. return
  1034. }
  1035. // 1.没有主将 或 主将为步兵/骑兵,主将上阵2(下标1)号位;副将1和副将2分别上阵4(下标3)号位和6(下标5)号位 当前为默认值 下标从0开始
  1036. teamIndex1, teamIndex2, teamIndex3 := int32(1), int32(3), int32(5)
  1037. teamMember, found := team.GetTeamMember(constant.TeamMemberTeamIndex_Start)
  1038. if found {
  1039. // 获取主将兵种
  1040. hero, found := playerTable.Heroes.Get(teamMember.HeroGUID)
  1041. if found {
  1042. heroRow, found := data.Hero.GetByID(hero.HeroID)
  1043. if found {
  1044. //2.主将为弓兵,主将上阵5(下标4)号位;副将1和副将2分别上阵1(下标0)号位和3(下标2)号位 下标从0开始
  1045. if heroRow.Job == int32(enum.Job_Archer) {
  1046. teamIndex1, teamIndex2, teamIndex3 = 4, 0, 2
  1047. }
  1048. } else {
  1049. clog.Warnf("[RefreshTeamLineup]hero config not found, playerID:%d, heroID: %d", playerTable.PlayerID, hero.HeroID)
  1050. }
  1051. } else {
  1052. clog.Warnf("[RefreshTeamLineup] hero not found, playerID:%d, heroGUID: %d", playerTable.PlayerID, teamMember.HeroGUID)
  1053. }
  1054. teamMember.Index = teamIndex1
  1055. }
  1056. teamMember2, found := team.GetTeamMember(constant.TeamMemberIndex_2)
  1057. if found {
  1058. teamMember2.Index = teamIndex2
  1059. }
  1060. teamMember3, found := team.GetTeamMember(constant.TeamMemberIndex_3)
  1061. if found {
  1062. teamMember3.Index = teamIndex3
  1063. }
  1064. }
  1065. // 检测同队伍是否存在相同英雄
  1066. func (p *service) CheckTeamSameHero(playerTable *db.MapPlayerTable, heroID, srcTeamID, srcTeamIndex, excludeTeamID, excludeIndex int32) bool {
  1067. for _, team := range playerTable.Teams {
  1068. for _, member := range team.Members {
  1069. // 排除原来的位置
  1070. if srcTeamID == team.TeamID && srcTeamIndex == member.TeamIndex {
  1071. continue
  1072. }
  1073. // 排除现在上阵的队伍位置
  1074. if team.TeamID == excludeTeamID && member.TeamIndex == excludeIndex {
  1075. continue
  1076. }
  1077. hero, ok := playerTable.Heroes.Get(member.HeroGUID)
  1078. if !ok {
  1079. // 没找到返回true 防止上阵相同英雄
  1080. return true
  1081. }
  1082. if hero.HeroID == heroID {
  1083. return true
  1084. }
  1085. }
  1086. }
  1087. return false
  1088. }
  1089. // 英雄下阵处理
  1090. func (p *service) DoTeamLeave(playerTable *db.MapPlayerTable, team *dbMapPlayer.Team, teamIndex int32) (*dbMapPlayer.Hero, int32) {
  1091. teamMember, ok := team.GetTeamMember(teamIndex)
  1092. // 下阵位置不存在英雄
  1093. if !ok || teamMember.HeroGUID == 0 {
  1094. return nil, code.OK
  1095. }
  1096. heroEntity, ok := playerTable.Heroes.Get(teamMember.HeroGUID)
  1097. if !ok {
  1098. return nil, code.HeroNotExist
  1099. }
  1100. team.DelTeamMember(teamIndex)
  1101. heroEntity.TeamID = 0
  1102. if teamMember.TroopsNum > 0 {
  1103. num := int64(teamMember.TroopsNum)
  1104. // 返还兵力 直接返还兵力
  1105. mapFacade.Storage.AssetAddOne(playerTable.PlayerID, teamMember.TroopsID, num, ao.TeamLeaveTroopsReturn)
  1106. push := &pb.AssetList{}
  1107. push.List = append(push.List, &pb.Asset{
  1108. Id: teamMember.TroopsID,
  1109. Num: num,
  1110. })
  1111. // 推送返还兵力
  1112. sessions.PushPlayer(playerTable.PlayerID, nameRoute.PushMapPlayer_TeamTroopReturn, push)
  1113. }
  1114. return heroEntity, code.OK
  1115. }
  1116. // 检测是否可以城外征兵
  1117. func (p *service) CheckCanOusideConscript(playerID int64, configID int32) bool {
  1118. mapBuildRow, found := data.MapBuild.GetByConfigID(configID)
  1119. if found {
  1120. return mapBuildRow.CanOutsideConscript
  1121. }
  1122. // 地图建筑表没找到 找地图-城池表
  1123. mapCastleRow, found := data.MapCastle.GetByConfigID(configID)
  1124. if !found {
  1125. clog.Warnf("[CheckCanOusideConscript] map castle not found. playerID:%d, configID:%d", playerID, configID)
  1126. return false
  1127. }
  1128. return mapCastleRow.CanOutsideConscript
  1129. }
  1130. // 是否兵力不足
  1131. func (p *service) IsTeamTroopsLack(playerTable *db.MapPlayerTable, team *dbMapPlayer.Team) bool {
  1132. var (
  1133. total int32
  1134. current = team.GetTeamTroopCount()
  1135. )
  1136. for _, member := range team.Members {
  1137. heroEntity, found := playerTable.Heroes.Get(member.HeroGUID)
  1138. if !found {
  1139. clog.Warnf("[IsTeamTroopsLack] fail, hero not found. playerID:%d, heroGUID:%d", playerTable.PlayerID, member.HeroGUID)
  1140. return false
  1141. }
  1142. heroTroopsNum := p.GetHeroTroopsNum(playerTable.PlayerID, heroEntity.HeroID, member.TeamIndex)
  1143. total += heroTroopsNum
  1144. }
  1145. ratio := float32(current) / float32(total) * 10000.0
  1146. return ratio <= data.Const.TeamTroopsLackRatio
  1147. }