yuanhang_trial.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. package model
  2. import (
  3. "container/heap"
  4. "math/rand"
  5. "rocommon/util"
  6. "roserver/baseserver/model"
  7. "roserver/baseserver/set"
  8. "roserver/serverproto"
  9. "sort"
  10. )
  11. //远航试炼
  12. const (
  13. YuanHangTrialViewMaxNum = 200 //最大客户端可视列表数量
  14. //1 你被xxx挑战,失去xxx奖励
  15. //2 你对xx发起了抢夺,获得了xxx奖励
  16. //3 你对xx发起了抢夺,但是自己无法获取到奖励
  17. TrialLogDataType_1 = 1
  18. TrialLogDataType_2 = 2
  19. TrialLogDataType_3 = 3
  20. )
  21. type yuanHangTrialList struct {
  22. isDirty bool
  23. itemList []*serverproto.YuanHangTrialData
  24. trailItemList map[uint64]*serverproto.YuanHangTrialData
  25. }
  26. func (this *yuanHangTrialList) addItem(item *serverproto.YuanHangTrialData) {
  27. this.itemList = append(this.itemList, item)
  28. this.isDirty = true
  29. this.trailItemList[item.Uid] = item
  30. }
  31. func (this *yuanHangTrialList) delItem(uid uint64) {
  32. //后续使用二分查找实现
  33. for idx := 0; idx < len(this.itemList); idx++ {
  34. if this.itemList[idx].Uid == uid {
  35. this.itemList = append(this.itemList[idx:], this.itemList[:idx+1]...)
  36. this.isDirty = true
  37. break
  38. }
  39. }
  40. delete(this.trailItemList, uid)
  41. }
  42. func (this *yuanHangTrialList) binarySearch(leftIndex, rightIndex int, findVal uint64) int {
  43. if leftIndex > rightIndex {
  44. return 0
  45. }
  46. middle := (leftIndex + rightIndex) / 2
  47. if this.itemList[middle].DirtyStamp > findVal {
  48. this.binarySearch(middle+1, rightIndex, findVal)
  49. } else if this.itemList[middle].DirtyStamp < findVal {
  50. this.binarySearch(leftIndex, middle-1, findVal)
  51. }
  52. return middle
  53. }
  54. //minheap
  55. type TrailItemMinHeap struct {
  56. ItemList []*serverproto.YuanHangTrialData
  57. }
  58. func (h *TrailItemMinHeap) Len() int {
  59. return len(h.ItemList)
  60. }
  61. func (h *TrailItemMinHeap) Less(i, j int) bool {
  62. return h.ItemList[i].EndTimeStamp < h.ItemList[j].EndTimeStamp
  63. }
  64. func (h *TrailItemMinHeap) Swap(i, j int) {
  65. h.ItemList[i], h.ItemList[j] = h.ItemList[j], h.ItemList[i]
  66. }
  67. func (h *TrailItemMinHeap) Push(item interface{}) {
  68. h.ItemList = append(h.ItemList, item.(*serverproto.YuanHangTrialData))
  69. }
  70. func (h *TrailItemMinHeap) Pop() (ret interface{}) {
  71. l := len(h.ItemList)
  72. h.ItemList, ret = h.ItemList[:l-1], h.ItemList[l-1]
  73. return
  74. }
  75. ////
  76. type YuanHangTrialManager struct {
  77. trailItemMinHeap *TrailItemMinHeap
  78. tailItemLeftList *TrailItemMinHeap //超过200后的存储列表(排序)
  79. trialItemMapList map[uint64]*serverproto.YuanHangTrialData //total
  80. //cache fightInfo
  81. cacheFightRoleInfoList map[uint64]*serverproto.FightRoleInfo
  82. dbChangeTrialItemList set.Interface
  83. lineNum int32
  84. latestTrialTime uint64 //玩家最近一次的远航操作时间戳
  85. notifyList []*serverproto.YuanHangTrialData
  86. bInit bool
  87. updateTimer util.ServerTimer //主循环定时器
  88. }
  89. func newYuanHangTrialManager() *YuanHangTrialManager {
  90. mag := &YuanHangTrialManager{
  91. trialItemMapList: map[uint64]*serverproto.YuanHangTrialData{},
  92. trailItemMinHeap: &TrailItemMinHeap{},
  93. tailItemLeftList: &TrailItemMinHeap{},
  94. cacheFightRoleInfoList: map[uint64]*serverproto.FightRoleInfo{},
  95. dbChangeTrialItemList: set.New(set.NonThreadSafe),
  96. }
  97. mag.updateTimer = util.NewDurationTimer(util.GetCurrentTime(), 1000)
  98. heap.Init(mag.trailItemMinHeap)
  99. heap.Init(mag.tailItemLeftList)
  100. return mag
  101. }
  102. func (this *YuanHangTrialManager) Update(ms uint64) {
  103. if !this.bInit {
  104. if this.initTrialDataFromDB() {
  105. this.bInit = true
  106. }
  107. return
  108. }
  109. if !this.updateTimer.IsStart() || !this.updateTimer.IsExpired(ms) {
  110. return
  111. }
  112. this.refreshViewList(ms)
  113. //save change uid
  114. this.saveItemData()
  115. //notify list
  116. if len(this.notifyList) > 0 {
  117. ssNotifyMsg := &serverproto.SSCrossYuanHangTrialViewNtf{}
  118. ssNotifyMsg.TrialViewList = append(ssNotifyMsg.TrialViewList, this.notifyList...)
  119. SendAllZoneSocial(ssNotifyMsg)
  120. this.notifyList = this.notifyList[:0]
  121. }
  122. }
  123. //服务器启动时数据初始化
  124. func (this *YuanHangTrialManager) initTrialDataFromDB() bool {
  125. tmpItemList, bOk := GetTrialItemFromRedis()
  126. if !bOk {
  127. return false
  128. }
  129. sort.Slice(tmpItemList, func(i, j int) bool {
  130. return tmpItemList[i].EndTimeStamp < tmpItemList[j].EndTimeStamp
  131. })
  132. for idx := 0; idx < len(tmpItemList); idx++ {
  133. if idx < YuanHangTrialViewMaxNum {
  134. heap.Push(this.trailItemMinHeap, tmpItemList[idx])
  135. } else {
  136. heap.Push(this.tailItemLeftList, tmpItemList[idx])
  137. }
  138. this.trialItemMapList[tmpItemList[idx].Uid] = tmpItemList[idx]
  139. }
  140. return true
  141. }
  142. func (this *YuanHangTrialManager) refreshViewList(ms uint64) {
  143. //更新可视列表中是否有玩家远航结束
  144. if this.trailItemMinHeap.Len() <= 0 && this.tailItemLeftList.Len() <= 0 {
  145. return
  146. }
  147. //当前可视列表中过期的玩家移除
  148. for {
  149. if this.trailItemMinHeap.Len() <= 0 {
  150. break
  151. }
  152. itemInfo := heap.Pop(this.trailItemMinHeap).(*serverproto.YuanHangTrialData)
  153. if itemInfo == nil {
  154. break
  155. }
  156. //玩家远航结束,需要添加新玩家到可视列表中
  157. if ms >= itemInfo.EndTimeStamp {
  158. if this.tailItemLeftList.Len() > 0 {
  159. addItemInfo := heap.Pop(this.tailItemLeftList).(*serverproto.YuanHangTrialData)
  160. if addItemInfo != nil {
  161. heap.Push(this.trailItemMinHeap, addItemInfo)
  162. this.dbChangeTrialItemList.Add(addItemInfo.Uid)
  163. //添加到通知列表中
  164. this.addNotifyList(addItemInfo)
  165. }
  166. }
  167. this.dbChangeTrialItemList.Add(itemInfo.Uid)
  168. } else {
  169. heap.Push(this.trailItemMinHeap, itemInfo)
  170. break
  171. }
  172. }
  173. //添加新玩家到可视列表中(填充可视列表最大数量)
  174. needAddNum := YuanHangTrialViewMaxNum - this.trailItemMinHeap.Len()
  175. if needAddNum > 0 {
  176. for idx := 0; idx < needAddNum; idx++ {
  177. if this.tailItemLeftList.Len() <= 0 {
  178. break
  179. }
  180. addItemInfo := heap.Pop(this.tailItemLeftList).(*serverproto.YuanHangTrialData)
  181. if addItemInfo != nil {
  182. heap.Push(this.trailItemMinHeap, addItemInfo)
  183. //添加到通知列表中
  184. this.addNotifyList(addItemInfo)
  185. }
  186. }
  187. }
  188. }
  189. func (this *YuanHangTrialManager) saveItemData() {
  190. if this.dbChangeTrialItemList.Size() <= 0 {
  191. return
  192. }
  193. saveList := this.dbChangeTrialItemList.List()
  194. for idx := 0; idx < len(saveList); idx++ {
  195. saveUid := saveList[idx].(uint64)
  196. if item, ok := this.trialItemMapList[saveUid]; ok {
  197. SetTrialItemToRedis(item, saveUid)
  198. } else {
  199. //数据库中需要在获取奖励之后才删除
  200. //SetTrialItemToRedis(nil, saveUid)
  201. }
  202. }
  203. this.dbChangeTrialItemList.Clear()
  204. }
  205. func (this *YuanHangTrialManager) addTrialItem(uid uint64, trialType int32, endTimeStamp uint64, durationTime int32,
  206. zone, fromZone int32) *serverproto.YuanHangTrialData {
  207. nowTime := util.GetTimeMilliseconds()
  208. this.lineNum = (this.lineNum+1)%10 + 1
  209. if this.latestTrialTime <= 0 {
  210. this.latestTrialTime = nowTime
  211. } else if this.latestTrialTime+30000 < nowTime {
  212. this.latestTrialTime = nowTime
  213. this.lineNum = rand.Int31n(10) + 1
  214. }
  215. item := &serverproto.YuanHangTrialData{
  216. Uid: uid,
  217. TrialType: trialType,
  218. DirtyStamp: nowTime,
  219. EndTimeStamp: endTimeStamp,
  220. Zone: zone,
  221. FromRealZone: fromZone,
  222. DurationTime: durationTime,
  223. LineNum: this.lineNum,
  224. }
  225. this.trialItemMapList[item.Uid] = item
  226. //添加到可见列表中
  227. if this.trailItemMinHeap.Len() < YuanHangTrialViewMaxNum {
  228. heap.Push(this.trailItemMinHeap, item)
  229. this.addNotifyList(item)
  230. } else {
  231. heap.Push(this.tailItemLeftList, item)
  232. }
  233. this.dbChangeTrialItemList.Add(uid)
  234. return item
  235. }
  236. func (this *YuanHangTrialManager) addNotifyList(notifyItem *serverproto.YuanHangTrialData) {
  237. for idx := 0; idx < len(this.notifyList); idx++ {
  238. if this.notifyList[idx].Uid == notifyItem.Uid {
  239. this.notifyList[idx] = notifyItem
  240. return
  241. }
  242. }
  243. this.notifyList = append(this.notifyList, notifyItem)
  244. }
  245. func (this *YuanHangTrialManager) getFightRoleInfo(uid uint64) *serverproto.FightRoleInfo {
  246. if fightInfo, ok := this.cacheFightRoleInfoList[uid]; ok {
  247. return fightInfo
  248. }
  249. err, fightInfo := GetFightInfoFromRedis(uid)
  250. if err != nil {
  251. util.InfoF("getFightRoleInfo uid=%v err=%v", uid, err)
  252. return nil
  253. }
  254. this.cacheFightRoleInfoList[uid] = fightInfo
  255. return fightInfo
  256. }
  257. //获取远航可视列表
  258. func (this *YuanHangTrialManager) GetTrialViewList(bViewList bool, uidTrialInfo uint64) ([]*serverproto.YuanHangTrialData, *serverproto.YuanHangTrialData) {
  259. if uidTrialInfo > 0 {
  260. if item, ok := this.trialItemMapList[uidTrialInfo]; ok {
  261. if bViewList {
  262. return this.trailItemMinHeap.ItemList, item
  263. } else {
  264. return nil, item
  265. }
  266. }
  267. }
  268. if bViewList {
  269. return this.trailItemMinHeap.ItemList, nil
  270. }
  271. return nil, nil
  272. }
  273. //发起远航试炼
  274. // @trialType 远航类型 (1,2,3,4,5...)
  275. func (this *YuanHangTrialManager) YuanHangTrial(fromZone int32, uid uint64, trialType int32, endTimeStamp uint64,
  276. fightInfo *serverproto.FightRoleInfo, durationTime int32) *serverproto.YuanHangTrialData {
  277. zone := fightInfo.BriefInfo.SelectZone
  278. if zone <= 0 {
  279. zone = fromZone
  280. }
  281. addTrialItem := this.addTrialItem(uid, trialType, endTimeStamp, durationTime, zone, fromZone)
  282. err := SetFightInfoToRedis(uid, fightInfo)
  283. util.InfoF("uid=%v YuanHangTrial err=%v addTrialItem=%v", uid, err, addTrialItem)
  284. this.cacheFightRoleInfoList[uid] = fightInfo
  285. //notify other
  286. return addTrialItem
  287. }
  288. //非正常情况下不调用该接口
  289. func (this *YuanHangTrialManager) YuanHangTrialForceSelf(ssMsg *serverproto.SSCrossYuanHangTrialSelfReq, fromZone int32) {
  290. _, ok := this.trialItemMapList[ssMsg.SelfUid]
  291. if !ok {
  292. nowTime := util.GetTimeMilliseconds()
  293. if ssMsg.EndTimeStamp > nowTime {
  294. zone := ssMsg.SelfFightInfo.BriefInfo.SelectZone
  295. if zone <= 0 {
  296. zone = fromZone
  297. }
  298. addTrialItem := this.addTrialItem(ssMsg.SelfUid, ssMsg.TrialType, ssMsg.EndTimeStamp, ssMsg.DurationTime, zone, fromZone)
  299. //err := SetFightInfoToRedis(ssMsg.SelfUid, ssMsg.SelfFightInfo)
  300. util.InfoF("uid=%v YuanHangTrialForceSelf addTrialItem=%v", ssMsg.SelfUid, addTrialItem)
  301. this.cacheFightRoleInfoList[ssMsg.SelfUid] = ssMsg.SelfFightInfo
  302. }
  303. }
  304. SetFightInfoToRedis(ssMsg.SelfUid, ssMsg.SelfFightInfo)
  305. }
  306. //飞艇信息
  307. //trialUid 飞艇对应玩家
  308. //trialUidEndTime 当前玩家飞艇对应的结束时间
  309. func (this *YuanHangTrialManager) GetTrialInfo(trialUid, trialUidEndTime uint64) (serverproto.ErrorCode, *serverproto.YuanHangTrialData) {
  310. trialItem, ok := this.trialItemMapList[trialUid]
  311. if !ok {
  312. //必须是该玩家的本次远航试炼
  313. return serverproto.ErrorCode_ERROR_CROSS_YUANHANGTRIAL_NOT_JOIN, nil
  314. }
  315. nowTime := util.GetTimeMilliseconds()
  316. if trialItem.EndTimeStamp <= nowTime {
  317. //本次查询的玩家对应的飞艇已经结束
  318. return serverproto.ErrorCode_ERROR_CROSS_YUANHANGTRIAL_NOT_JOIN, nil
  319. }
  320. return serverproto.ErrorCode_ERROR_OK, trialItem
  321. }
  322. // @param selfUid 发起挑战玩家
  323. // @param challengeUid 被挑战玩家
  324. // @param beChallengeMaxNum 最大被挑战次数
  325. func (this *YuanHangTrialManager) GetChallengeTrialItem(selfUid, challengeUid, challengeUidEndTime uint64,
  326. beChallengeMaxNum int32, ssAckMsg *serverproto.SSCrossYuanHangTrialChallengeAck) serverproto.ErrorCode {
  327. trialItem, ok := this.trialItemMapList[challengeUid]
  328. if !ok || trialItem.EndTimeStamp < challengeUidEndTime {
  329. //必须是该玩家的本次远航试炼
  330. util.InfoF("uid=%v GetChallengeTrialItem challenge item not exist existUidAndTime[%v,%v] not[%v,%v]", selfUid,
  331. trialItem.Uid, trialItem.EndTimeStamp, challengeUid, challengeUidEndTime)
  332. return serverproto.ErrorCode_ERROR_CROSS_YUANHANGTRIAL_NOT_JOIN
  333. }
  334. nowTime := util.GetTimeMilliseconds()
  335. if trialItem.EndTimeStamp <= nowTime {
  336. return serverproto.ErrorCode_ERROR_CROSS_YUANHANGTRIAL_NOT_JOIN
  337. }
  338. //是否已经抢夺过一次该玩家
  339. for idx := 0; idx < len(trialItem.BeChallengeUidList); idx++ {
  340. if trialItem.BeChallengeUidList[idx] == selfUid {
  341. return serverproto.ErrorCode_ERROR_CROSS_YUANHANGTRIAL_CHALLENGED
  342. }
  343. }
  344. //抢夺次数上限判断
  345. if beChallengeMaxNum <= 0 {
  346. convertData, ok := model.ConvertYuanHangTrail[trialItem.TrialType]
  347. if ok {
  348. beChallengeMaxNum = convertData.BeAttackNum
  349. }
  350. }
  351. if trialItem.BeChallengeNum >= beChallengeMaxNum {
  352. return serverproto.ErrorCode_ERROR_CORSS_YUANHANGTRIAL_BE_CHALLENGE_LIMIT
  353. }
  354. //获取挑战玩家信息
  355. fightInfo := this.getFightRoleInfo(challengeUid)
  356. if fightInfo == nil {
  357. return serverproto.ErrorCode_ERROR_CROSS_YUANHANGTRIAL_NOT_JOIN
  358. }
  359. ssAckMsg.FightInfo = fightInfo
  360. ssAckMsg.ChallengeUid = challengeUid
  361. ssAckMsg.ChallengeUidEndTime = challengeUidEndTime
  362. return serverproto.ErrorCode_ERROR_OK
  363. }
  364. //挑战结果通知
  365. func (this *YuanHangTrialManager) ChallengeTrialItemResult(selfUid, challengeUid, challengeUidEndTime uint64,
  366. selfNickName string, selfZone int32, bWin bool, fightInfo *serverproto.FightRoleInfo, ssAckMsg *serverproto.SSCrossYuanHangTrialChallengeResultAck) serverproto.ErrorCode {
  367. trialItem, ok := this.trialItemMapList[challengeUid]
  368. if !ok || trialItem.EndTimeStamp < challengeUidEndTime {
  369. //必须是该玩家的本次远航试炼
  370. return serverproto.ErrorCode_ERROR_CROSS_YUANHANGTRIAL_NOT_JOIN
  371. }
  372. nowTime := util.GetTimeMilliseconds()
  373. if trialItem.EndTimeStamp <= nowTime {
  374. return serverproto.ErrorCode_ERROR_CROSS_YUANHANGTRIAL_NOT_JOIN
  375. }
  376. convertData, ok := model.ConvertYuanHangTrail[trialItem.TrialType]
  377. if ok {
  378. if trialItem.BeChallengeNum >= convertData.BeAttackNum {
  379. return serverproto.ErrorCode_ERROR_CORSS_YUANHANGTRIAL_BE_CHALLENGE_LIMIT
  380. }
  381. }
  382. SetFightInfoToRedis(selfUid, fightInfo)
  383. this.cacheFightRoleInfoList[selfUid] = fightInfo
  384. //挑战日志记录
  385. beChallengePlayer := this.getFightRoleInfo(trialItem.Uid)
  386. if beChallengePlayer != nil {
  387. ssAckMsg.LogData = &serverproto.YuanHangTrialLogData{
  388. Type: TrialLogDataType_2,
  389. RecordTime: nowTime,
  390. State: true,
  391. TargetPlayerUid: trialItem.Uid,
  392. TargetPlayerZone: trialItem.Zone,
  393. TargetPlayerName: beChallengePlayer.BriefInfo.NickName,
  394. TrialType: trialItem.TrialType,
  395. }
  396. }
  397. ssAckMsg.ShipTrialLevel = trialItem.TrialType
  398. trialItem.BeChallengeNum++
  399. trialItem.BeChallengeUidList = append(trialItem.BeChallengeUidList, selfUid)
  400. trialItem.DirtyStamp = nowTime
  401. this.dbChangeTrialItemList.Add(challengeUid)
  402. this.addNotifyList(trialItem)
  403. //被挑战日志记录
  404. //通知被挑战玩家
  405. this.addBeChallengeLog(trialItem, nowTime, selfUid, selfZone, selfNickName)
  406. return serverproto.ErrorCode_ERROR_OK
  407. }
  408. func (this *YuanHangTrialManager) UpdateRankScore(uid, rankScore uint64) {
  409. UpdateTrialRankScore(uid, rankScore)
  410. }
  411. //远航试炼排行榜
  412. func (this *YuanHangTrialManager) GetTrialRankList(uid uint64, startIdx int32,
  413. ssAckMsg *serverproto.SSCrossYuanHangTrialRankListAck) {
  414. selfRank, selfScore, totalRank, rankList := GetTrialRankList(uid, startIdx)
  415. ssAckMsg.SelfRank = selfRank
  416. ssAckMsg.SelfScore = selfScore
  417. ssAckMsg.TotalRank = totalRank
  418. ssAckMsg.RankList = rankList
  419. }
  420. func (this *YuanHangTrialManager) addBeChallengeLog(trialItem *serverproto.YuanHangTrialData,
  421. nowTime uint64, targetPlayerUid uint64, targetPlayerZone int32, targetPlayerNickName string) {
  422. //通知被挑战玩家
  423. logNtf := &serverproto.SSCrossYuanHangTrialLogNtf{
  424. NtfUid: trialItem.Uid,
  425. }
  426. logNtf.LogData = &serverproto.YuanHangTrialLogData{
  427. Type: TrialLogDataType_1,
  428. RecordTime: nowTime,
  429. State: true,
  430. TargetPlayerUid: targetPlayerUid,
  431. TargetPlayerZone: targetPlayerZone,
  432. TargetPlayerName: targetPlayerNickName,
  433. TrialType: trialItem.TrialType,
  434. }
  435. //失去的奖励
  436. convertData, ok := model.ConvertYuanHangTrail[trialItem.TrialType]
  437. if ok {
  438. logNtf.LogData.ItemList = append(logNtf.LogData.ItemList, convertData.BeAttackLoseItemListSlice...)
  439. }
  440. util.InfoF("uid=%v addBeChallengeLog trialType=%v beAttackedUid=%v realZone=%v", targetPlayerUid, trialItem.TrialType, trialItem.Uid, trialItem.FromRealZone)
  441. SendZoneSocial(logNtf, trialItem.FromRealZone)
  442. }