service.go 37 KB

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