player_chapter.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  1. package player
  2. import (
  3. "f1-game/internal/battle"
  4. capp "f1-game/internal/cherry_app"
  5. "f1-game/internal/code"
  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. nameEvent "f1-game/internal/name/event"
  12. nameLocal "f1-game/internal/name/local"
  13. "f1-game/internal/pb"
  14. "f1-game/internal/types"
  15. "f1-game/nodes/game/player/asset"
  16. ao "f1-game/nodes/game/player/asset/origin"
  17. "f1-game/nodes/game/player/chapter"
  18. "fmt"
  19. "time"
  20. ctime "github.com/cherry-game/cherry/extend/time"
  21. cfacade "github.com/cherry-game/cherry/facade"
  22. cproto "github.com/cherry-game/cherry/net/proto"
  23. )
  24. func (p *actorPlayer) initChapter() {
  25. p.Local().Register(nameLocal.Player_ChapterBattle, p.chapterBattle)
  26. p.Local().Register(nameLocal.Player_ChapterVerify, p.chapterVerify)
  27. p.Local().Register(nameLocal.Player_ChapterLevelSweep, p.chapterLevelSweep)
  28. p.Local().Register(nameLocal.Player_ChapterStarReward, p.chapterReward)
  29. p.Local().Register(nameLocal.Player_ChapterLevelTimesBuy, p.chapterLevelTimesBuy)
  30. p.Local().Register(nameLocal.Player_ChapterLastRecoverTime, p.chapterLastRecoverTime)
  31. p.Local().Register(nameLocal.Player_ChapterTakeOutStamina, p.chapterTakeOutStamina)
  32. p.EventRegister(nameEvent.Vip_LevelUp, p.chapterRefreshEvent)
  33. }
  34. // 发起探险战斗
  35. func (p *actorPlayer) chapterBattle(session *cproto.Session, req *pb.I32) {
  36. var (
  37. levelID = req.Value
  38. lineupID = constant.Adventure_Type // 阵容ID默认填1,如果后面扩展了再由前端传入
  39. )
  40. errCode := p.CheckChapterEnter(levelID)
  41. if code.IsFail(errCode) {
  42. p.ResponseCode(session, errCode)
  43. return
  44. }
  45. playerBattleParam, errCode := chapter.Service().GetChapterBattleCampParam(p.playerID, lineupID)
  46. if code.IsFail(errCode) {
  47. p.ResponseCode(session, errCode)
  48. return
  49. }
  50. chapterLevelRow, found := data.ChapterLevel.GetByID(levelID)
  51. if !found {
  52. p.ResponseCode(session, code.ConfigNotFound_ChapterLevel)
  53. return
  54. }
  55. var (
  56. chapterTable = p.ChapterTable()
  57. monsterGroupID = chapterLevelRow.MonsterGroupID
  58. battleSceneID = chapterLevelRow.BattleSceneID
  59. )
  60. monsterBattleParam := data.BuildBattleCampParam(monsterGroupID, enum.BattleCampType_Right)
  61. if len(monsterBattleParam.Squads) < 1 {
  62. p.ResponseCode(session, code.ChapterMonsterGroupConfigError)
  63. return
  64. }
  65. // 缓存关卡ID和随机种子
  66. battleRandomSeed := time.Now().UnixNano()
  67. battleParam := &pb.BattleParam{
  68. BattleType: enum.BattleType_Chapter,
  69. RandomSeed: uint64(battleRandomSeed),
  70. MapID: battleSceneID,
  71. Camps: []*pb.BattleCampParam{
  72. playerBattleParam,
  73. monsterBattleParam,
  74. },
  75. }
  76. // 发起战斗就直接去验证服获取校验码
  77. recordID := fmt.Sprintf("pve_%d_%d_%s", p.playerID, levelID, ctime.Now().ToShortDateTimeFormat())
  78. validation := battle.Validation(recordID, battleParam)
  79. if validation == nil {
  80. p.ResponseCode(session, code.ChapterBattleError)
  81. return
  82. }
  83. chapterTable.Chapter.SetBattleData(levelID, validation.Checksum)
  84. chapterTable.Save2Queue()
  85. p.Response(session, battleParam)
  86. }
  87. // 探险战斗验证
  88. func (p *actorPlayer) chapterVerify(session *cproto.Session, req *pb.BattleResult) {
  89. var (
  90. chapterTable = p.ChapterTable()
  91. )
  92. // 没有挑战过,返回失败
  93. if chapterTable.Chapter.BattleLevelID == 0 || chapterTable.Chapter.Checksum == 0 {
  94. p.ResponseCode(session, code.IllegalArgument)
  95. return
  96. }
  97. if capp.IsBattleValidation() && chapterTable.Chapter.Checksum != req.Checksum {
  98. p.Debug("chapterVerify checksum not match. req.Checksum = %d server.Checksum = %d", req.Checksum, chapterTable.Chapter.Checksum)
  99. p.ResponseCode(session, code.ChapterBattleResultVerifyFail)
  100. return
  101. }
  102. var (
  103. isWin = req.VictoriousCamp == enum.BattleCampType_Left
  104. rsp = &pb.ChapterVerifyResponse{
  105. Result: isWin,
  106. }
  107. )
  108. if isWin {
  109. challengeLevel, errCode := p.ChapterBattleResult(session, req)
  110. if code.IsFail(errCode) {
  111. p.ResponseCode(session, errCode)
  112. return
  113. }
  114. rsp.Challenge = challengeLevel
  115. }
  116. p.Response(session, rsp)
  117. }
  118. // 关卡扫荡
  119. func (p *actorPlayer) chapterLevelSweep(session *cproto.Session, req *pb.I32I32) {
  120. var (
  121. chapterTable = p.ChapterTable()
  122. levelID = req.Key
  123. sweepNum = req.Value
  124. )
  125. chapterLevelRow, found := data.ChapterLevel.GetByID(levelID)
  126. if !found {
  127. p.ResponseCode(session, code.ConfigNotFound_ChapterLevel)
  128. return
  129. }
  130. // 判断关卡是否解锁
  131. chapterLevel, found := chapterTable.Chapter.GetLevel(levelID)
  132. if !found {
  133. p.ResponseCode(session, code.ChapterLevelNotUnlocked)
  134. return
  135. }
  136. // 判断是否满星通关
  137. if chapterLevel.IsNotFullStar(len(chapterLevelRow.Conditions)) {
  138. p.ResponseCode(session, code.ChapterLevelNotFullStar)
  139. return
  140. }
  141. // 如果存在每日挑战次数上限
  142. if chapterLevelRow.DayChallengeCount > 0 {
  143. // 总挑战次数
  144. totalChallengeCount := chapterLevel.DayBuyChallengeCount + chapterLevelRow.DayChallengeCount
  145. // 判断条件次数是否满足
  146. if !chapterLevel.CheckChallengeCount(sweepNum, totalChallengeCount) {
  147. p.ResponseCode(session, code.ChapterLevelCountNotEnough)
  148. return
  149. }
  150. }
  151. var (
  152. beforeIsFull = chapterTable.Chapter.LastRecoverTime <= 0
  153. costs = chapterLevelRow.GetChapterLevelCost(sweepNum)
  154. )
  155. // 扣除精力
  156. _, errCode := asset.Service().Subs(p.playerID, costs, ao.ChapterLevelSweepCost)
  157. if code.IsFail(errCode) {
  158. p.ResponseCode(session, code.ChapterStaminaNotEnough)
  159. return
  160. }
  161. // 原来是满精力 判断扣除精力后精力是否还满
  162. if beforeIsFull {
  163. if full := chapter.Service().IsStaminaFull(p.playerID); !full {
  164. // 体力不满 更新上次精力的恢复时间
  165. chapterTable.Chapter.LastRecoverTime = ctime.Now().ToSecond()
  166. }
  167. }
  168. var (
  169. resp = &pb.ChapterLevelSweepReward{}
  170. rewards = types.Assets{}
  171. )
  172. // 获取扫荡奖励
  173. for num := int32(0); num < sweepNum; num++ {
  174. // 扫荡一次随机奖励
  175. singleRewards := chapterLevelRow.RandomChapterLevelReward()
  176. rewards.AddAssets(singleRewards)
  177. proto := singleRewards.ToAssetListProto()
  178. resp.Rewards = append(resp.Rewards, &proto)
  179. }
  180. _, errCode = asset.Service().Adds(p.playerID, rewards, ao.ChapterLevelSweepReward, true)
  181. if code.IsFail(errCode) {
  182. p.ResponseCode(session, errCode)
  183. return
  184. }
  185. chapterLevel.DayChallengeCount += sweepNum
  186. chapterTable.Chapter.FinishChallengeCount += sweepNum
  187. chapterTable.Chapter.TotalSweepCount += sweepNum
  188. chapterTable.Save2Queue()
  189. // 发送探险次数事件
  190. p.PostEvent(event.NewChapterChallenge(p.playerID, levelID, sweepNum, true))
  191. // 关卡信息更新 通知客户端
  192. chapter.Service().ChangePush(p.playerID, levelID)
  193. // 返回扫荡奖励
  194. p.Response(session, resp)
  195. }
  196. // 领取累计星级奖励
  197. func (p *actorPlayer) chapterReward(session *cproto.Session, req *pb.I32) {
  198. var (
  199. chapterTable = p.ChapterTable()
  200. chapterID = req.Value
  201. )
  202. chapterRow, found := data.Chapter.GetByID(chapterID)
  203. if !found {
  204. p.ResponseCode(session, code.ConfigNotFound_Chapter)
  205. return
  206. }
  207. // 判断章节是否解锁
  208. if !chapterTable.Chapter.IsChapterUnlocked(chapterID) {
  209. p.ResponseCode(session, code.ChapterNotUnlocked)
  210. return
  211. }
  212. chapterRewardRows, found := data.ChapterReward.ListByID(chapterRow.RewardPoolID)
  213. if !found {
  214. p.ResponseCode(session, code.ConfigNotFound_ChapterReward)
  215. return
  216. }
  217. var (
  218. // 当前星级
  219. curStars = chapterTable.Chapter.GetChapterStar(chapterRow.StartID, chapterRow.EndID)
  220. rewards = types.Assets{}
  221. stars = []int32{}
  222. )
  223. for _, chapterRewardRow := range chapterRewardRows {
  224. star := chapterRewardRow.StarNum
  225. // 判断章节星级奖励是否已领取 已领取跳过
  226. if chapterTable.Chapter.IsRewardReceived(chapterID, star) {
  227. continue
  228. }
  229. // 判断星数不足,直接退出
  230. if curStars < star {
  231. break
  232. }
  233. rewards.AddAssets(chapterRewardRow.Reward)
  234. stars = append(stars, star)
  235. }
  236. // 暂无可领取奖励
  237. if rewards.IsEmpty() {
  238. p.ResponseCode(session, code.ChapterRewardIsEmpty)
  239. return
  240. }
  241. // 领取奖励
  242. _, errCode := asset.Service().Adds(p.playerID, rewards, ao.ChapterStarRewardGet, true)
  243. if code.IsFail(errCode) {
  244. p.ResponseCode(session, errCode)
  245. return
  246. }
  247. // 更新领取信息
  248. chapterReward := chapterTable.Chapter.ReceiveChapterReward(chapterID, stars...)
  249. chapterTable.Save2Queue()
  250. resp := chapterReward.ToChapterRewardProto()
  251. p.Response(session, resp)
  252. }
  253. // 探险关卡挑战次数购买
  254. func (p *actorPlayer) chapterLevelTimesBuy(session *cproto.Session, req *pb.I32I32) {
  255. var (
  256. chapterTable = p.ChapterTable()
  257. levelID = req.Key
  258. buyTimes = req.Value
  259. )
  260. if buyTimes <= 0 {
  261. p.ResponseCode(session, code.ChapterLevelBuyTimesError)
  262. return
  263. }
  264. chapterLevelRow, found := data.ChapterLevel.GetByID(levelID)
  265. if !found {
  266. p.ResponseCode(session, code.ConfigNotFound_ChapterLevel)
  267. return
  268. }
  269. level, found := chapterTable.Chapter.GetLevel(levelID)
  270. if !found {
  271. p.ResponseCode(session, code.ChapterLevelNotUnlocked)
  272. return
  273. }
  274. // 超出每日购买次数上限
  275. if level.DayBuyChallengeCount+buyTimes > chapterLevelRow.DayCanBuyTimes {
  276. p.ResponseCode(session, code.ChapterLevelBuyTimesNotEnough)
  277. return
  278. }
  279. cost := data.Const.ChapterTimesBuyCost
  280. _, errCode := asset.Service().Sub(p.playerID, cost.ID, cost.Num*int64(buyTimes), ao.ChapterLevelTimesBuyCost)
  281. if code.IsFail(errCode) {
  282. p.ResponseCode(session, errCode)
  283. return
  284. }
  285. // 更新关卡挑战购买次数
  286. level.DayBuyChallengeCount += buyTimes
  287. chapterTable.Save2Queue()
  288. // 更新关卡信息 通知客户端
  289. chapter.Service().ChangePush(p.playerID, levelID)
  290. p.ResponseCode(session, code.OK)
  291. }
  292. // 精力池上一次恢复时间
  293. func (p *actorPlayer) chapterLastRecoverTime(session *cproto.Session, _ *pb.None) {
  294. chapterTable := p.ChapterTable()
  295. resp := &pb.I64{
  296. Value: chapterTable.Chapter.LastRecoverTime,
  297. }
  298. p.Response(session, resp)
  299. }
  300. // 取出精力
  301. func (p *actorPlayer) chapterTakeOutStamina(session *cproto.Session, req *pb.I64) {
  302. var (
  303. chapterTable = p.ChapterTable()
  304. takeOutNum = req.Value
  305. )
  306. if takeOutNum <= 0 {
  307. p.ResponseCode(session, code.IllegalArgument)
  308. return
  309. }
  310. // 获取缺少的精力值
  311. var (
  312. curStaminaNum = asset.Service().Get(p.playerID, data.Const.ChapterStaminaAddValue.ID)
  313. staminaLimit = chapter.Service().GetStaminaLimit(p.playerID)
  314. diffStamina = staminaLimit - curStaminaNum
  315. )
  316. // 当前精力已满
  317. if diffStamina <= 0 {
  318. p.ResponseCode(session, code.ChapterStaminaFull)
  319. return
  320. }
  321. // 实际取出的精力值
  322. takeOutNum = math.Min(takeOutNum, diffStamina)
  323. // 判断消耗是否足够
  324. _, errCode := asset.Service().Sub(p.playerID, enum.ItemIDTempStamina, takeOutNum, ao.ChapterStaminaTakeOutCost)
  325. if code.IsFail(errCode) {
  326. p.ResponseCode(session, errCode)
  327. return
  328. }
  329. // 恢复精力值
  330. _, errCode = asset.Service().Add(p.playerID, data.Const.ChapterStaminaAddValue.ID, takeOutNum, ao.ChapterStaminaTakeOutReward, true)
  331. if code.IsFail(errCode) {
  332. p.ResponseCode(session, errCode)
  333. return
  334. }
  335. // 判断是否需要定时恢复精力
  336. if chapterTable.Chapter.LastRecoverTime <= 0 {
  337. chapterTable.Chapter.LastRecoverTime = ctime.Now().ToSecond()
  338. chapterTable.Save2Queue()
  339. }
  340. resp := &pb.I64I64{
  341. Key: takeOutNum,
  342. Value: chapterTable.Chapter.LastRecoverTime,
  343. }
  344. p.Response(session, resp)
  345. }
  346. // vip升级触发精力恢复检测(vip提升精力&精力池上限)
  347. func (p *actorPlayer) chapterRefreshEvent(e cfacade.IEventData) {
  348. if _, ok := e.(*event.VipLevelUp); !ok {
  349. return
  350. }
  351. chapterTable := p.ChapterTable()
  352. if chapterTable.Chapter.LastRecoverTime > 0 {
  353. return
  354. }
  355. // 精力未满
  356. if staminaFull := chapter.Service().IsStaminaFull(p.playerID); !staminaFull {
  357. chapterTable.Chapter.LastRecoverTime = ctime.Now().ToSecond()
  358. chapterTable.Save2Queue()
  359. return
  360. }
  361. // 精力池未满
  362. if staminaPoolFull := chapter.Service().IsStaminaPoolFull(p.playerID); !staminaPoolFull {
  363. chapterTable.Chapter.LastRecoverTime = ctime.Now().ToSecond()
  364. chapterTable.Save2Queue()
  365. }
  366. }
  367. // 进入探险的检查
  368. func (p *actorPlayer) CheckChapterEnter(levelID int32) int32 {
  369. chapterLevelRow, found := data.ChapterLevel.GetByID(levelID)
  370. if !found {
  371. return code.ConfigNotFound_ChapterLevel
  372. }
  373. // 章节ID
  374. chapterID := chapterLevelRow.ChapterID
  375. chapterRow, found := data.Chapter.GetByID(chapterID)
  376. if !found {
  377. return code.ConfigNotFound_Chapter
  378. }
  379. // 验证当前章节是否有这个关卡
  380. if !chapterRow.VaildateLevel(levelID) {
  381. return code.ChapterLevelError
  382. }
  383. chapterTable := p.ChapterTable()
  384. // 章节是否已解锁
  385. if !chapterTable.Chapter.IsChapterUnlocked(chapterID) {
  386. return code.ChapterNotUnlocked
  387. }
  388. // 关卡是否已解锁
  389. if !chapterTable.Chapter.IsLevelUnlocked(levelID) {
  390. return code.ChapterLevelNotUnlocked
  391. }
  392. // 如果存在每日挑战次数上限 判断每日挑战次数是否达到上限
  393. if chapterLevelRow.DayChallengeCount > 0 {
  394. // 总挑战次数
  395. totalChallengeCount := chapterLevelRow.DayChallengeCount
  396. chapterLevel, found := chapterTable.Chapter.GetLevel(levelID)
  397. if found {
  398. totalChallengeCount += chapterLevel.DayBuyChallengeCount
  399. }
  400. // 判断是否存在进入次数 首次挑战也会扣除次数
  401. if !chapterTable.Chapter.CheckLevelChallengeCount(levelID, totalChallengeCount) {
  402. return code.ChapterLevelCountNotEnough
  403. }
  404. }
  405. // 检测精力是否足够
  406. errCode := asset.Service().IsAllEnough(p.playerID, chapterLevelRow.CostStamina)
  407. if code.IsFail(errCode) {
  408. return errCode
  409. }
  410. return code.OK
  411. }
  412. // 关卡挑战结果
  413. func (p *actorPlayer) ChapterBattleResult(session *cproto.Session, result *pb.BattleResult) (*pb.ChapterLevelChallenge, int32) {
  414. var (
  415. chapterTable = p.ChapterTable()
  416. levelID = chapterTable.Chapter.BattleLevelID
  417. )
  418. chapterLevelRow, found := data.ChapterLevel.GetByID(levelID)
  419. if !found {
  420. return nil, code.ConfigNotFound_ChapterLevel
  421. }
  422. chapterRow, found := data.Chapter.GetByID(chapterLevelRow.ChapterID)
  423. if !found {
  424. return nil, code.ConfigNotFound_Chapter
  425. }
  426. // 扣除体力前是否满体力
  427. beforeIsFull := chapterTable.Chapter.LastRecoverTime <= 0
  428. // 扣除精力
  429. _, errCode := asset.Service().Subs(p.playerID, chapterLevelRow.CostStamina, ao.ChapterLevelChallengeCost)
  430. if code.IsFail(errCode) {
  431. return nil, errCode
  432. }
  433. // 原来是满体力 判断扣除精力后精力是否还满
  434. if beforeIsFull {
  435. if full := chapter.Service().IsStaminaFull(p.playerID); !full {
  436. // 体力不满 更新上次精力的恢复时间
  437. chapterTable.Chapter.LastRecoverTime = ctime.Now().ToSecond()
  438. }
  439. }
  440. stars := chapter.Service().GetChapterStars(p.playerID, chapterLevelRow, result)
  441. // 更新关卡数据
  442. firstWin := chapterTable.Chapter.BattleEndHandle(levelID, stars)
  443. var (
  444. rewards types.Assets
  445. origin ao.Origin
  446. )
  447. // 首胜奖励
  448. if firstWin {
  449. rewards = chapterLevelRow.GetFirstRewards()
  450. origin = ao.ChapterLevelFirstReward
  451. } else {
  452. rewards = chapterLevelRow.RandomChapterLevelReward()
  453. origin = ao.ChapterLevelChallengeReward
  454. }
  455. // 发送首胜/挑战奖励
  456. _, errCode = asset.Service().Adds(p.playerID, rewards, origin, true)
  457. if code.IsFail(errCode) {
  458. p.Warn("chapter battle win add asset error: %v", errCode)
  459. }
  460. // 首胜检测是否更新下一章节或者下一关
  461. if firstWin {
  462. chapter.Service().CheckNextLevelOrChapter(chapterTable, chapterRow, levelID)
  463. }
  464. chapterTable.Chapter.FinishChallengeCount += 1
  465. // 清理战斗数据
  466. chapterTable.Chapter.ClearBattleData()
  467. chapterTable.Save2Queue()
  468. // 发送探险次数事件
  469. p.PostEvent(event.NewChapterChallenge(p.playerID, levelID, 1, false))
  470. level, _ := chapterTable.Chapter.GetLevel(levelID)
  471. challengeLevel := &pb.ChapterLevelChallenge{
  472. ChapterID: chapterTable.Chapter.ChapterID,
  473. LevelID: chapterTable.Chapter.LevelID,
  474. Level: level.ToChapterLevelProto(levelID),
  475. }
  476. // 奖励
  477. if !rewards.IsEmpty() {
  478. proto := rewards.ToAssetListProto()
  479. challengeLevel.Rewards = &proto
  480. }
  481. return challengeLevel, code.OK
  482. }