service.go 37 KB

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