player_hero.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. package player
  2. import (
  3. "f1-game/internal/code"
  4. "f1-game/internal/constant"
  5. "f1-game/internal/data"
  6. "f1-game/internal/enum"
  7. "f1-game/internal/event"
  8. nameEvent "f1-game/internal/name/event"
  9. nameLocal "f1-game/internal/name/local"
  10. nameRemote "f1-game/internal/name/remote"
  11. "f1-game/internal/pb"
  12. "f1-game/internal/types"
  13. "f1-game/nodes/game/internal/db"
  14. dbEquip "f1-game/nodes/game/internal/db/data/equip"
  15. dbHero "f1-game/nodes/game/internal/db/data/hero"
  16. gameFacade "f1-game/nodes/game/internal/facade"
  17. "slices"
  18. "f1-game/nodes/game/player/asset"
  19. ao "f1-game/nodes/game/player/asset/origin"
  20. "f1-game/nodes/game/player/equip"
  21. "f1-game/nodes/game/player/hero"
  22. cfacade "github.com/cherry-game/cherry/facade"
  23. cproto "github.com/cherry-game/cherry/net/proto"
  24. )
  25. func (p *actorPlayer) initHero() {
  26. p.Local().Register(nameLocal.Player_HeroStar, p.heroStar)
  27. p.Local().Register(nameLocal.Player_HeroSkillUp, p.heroSkillUp)
  28. p.Local().Register(nameLocal.Player_HeroWearEquip, p.heroWearEquip)
  29. p.Local().Register(nameLocal.Player_HeroTakeOffEquip, p.heroTakeOffEquip)
  30. p.EventRegister(nameEvent.Lord_LevelUp, p.heroLordLevelUp)
  31. }
  32. // heroStar 英雄升星
  33. func (p *actorPlayer) heroStar(session *cproto.Session, req *pb.HeroStarRequest) {
  34. if len(req.RemoveHeroGUIDs) < 1 {
  35. p.ResponseCode(session, code.HeroStarNotEnough)
  36. return
  37. }
  38. heroTable := p.HeroTable()
  39. starHero, ok := heroTable.Heroes.Get(req.HeroGUID)
  40. if !ok {
  41. p.ResponseCode(session, code.HeroNotExist)
  42. return
  43. }
  44. // 检查英雄是否可以升星
  45. if errCode := p.isHeroStarViable(starHero); code.IsFail(errCode) {
  46. p.ResponseCode(session, errCode)
  47. return
  48. }
  49. var (
  50. curStar = starHero.Star
  51. oldStar = curStar
  52. curStarExp = starHero.StarExp
  53. )
  54. // 检查材料
  55. for _, removeGUID := range req.RemoveHeroGUIDs {
  56. removeHero, ok := heroTable.Heroes.Get(removeGUID)
  57. if !ok {
  58. p.ResponseCode(session, code.HeroNotExist)
  59. return
  60. }
  61. // 判断英雄是否可以作为升星材料
  62. if errCode := p.isHeroStarMatch(starHero, removeHero); code.IsFail(errCode) {
  63. p.ResponseCode(session, errCode)
  64. return
  65. }
  66. // 计算星级经验
  67. starRow, ok := data.HeroStar.GetByStar(removeHero.Star)
  68. if !ok {
  69. p.Warn("[HeroStar] config not exists, star=%d", removeHero.Star)
  70. p.ResponseCode(session, code.ConfigNotFound_HeroStar)
  71. return
  72. }
  73. curStarExp += starRow.OfferExp
  74. }
  75. // 检查是否可以升星 (最多循环最大星级的次数)
  76. for range data.Const.HeroMaxStar {
  77. starRow, ok := data.HeroStar.GetByStar(curStar)
  78. if !ok {
  79. p.Warn("[HeroStar] config not exists, star=%d", curStar)
  80. p.ResponseCode(session, code.ConfigNotFound_HeroStar)
  81. return
  82. }
  83. if curStarExp < starRow.UpgradeExp {
  84. break
  85. }
  86. curStar++
  87. curStarExp -= starRow.UpgradeExp
  88. if curStar >= data.Const.HeroMaxStar {
  89. break
  90. }
  91. }
  92. // 地图队伍信息判断是否能删除英雄
  93. playerTable := p.PlayerTable()
  94. if len(playerTable.Map.MapNodeID) > 0 {
  95. req := &pb.I64List{
  96. List: req.RemoveHeroGUIDs,
  97. }
  98. errCode := p.mapPlayerCallWait(playerTable.Map.MapNodeID, nameRemote.MapPlayer_HeroDelete, req)
  99. if code.IsFail(errCode) {
  100. p.ResponseCode(session, errCode)
  101. return
  102. }
  103. }
  104. // 移除材料英雄
  105. hero.Service().RemoveHeros(p.playerID, req.RemoveHeroGUIDs...)
  106. starHero.StarExp = curStarExp
  107. if starHero.Star != curStar {
  108. starHero.Star = curStar
  109. //检测英雄技能激活
  110. hero.Service().CheckActiveHeroSkill(starHero)
  111. // 刷新英雄属性
  112. hero.Service().UpdateAttrs(p.playerID, starHero)
  113. // 更新英雄星级数量
  114. for addStar := oldStar + 1; addStar <= starHero.Star; addStar++ {
  115. heroTable.HeroExtend.IncrStar(addStar)
  116. }
  117. // 发送英雄升星事件
  118. p.PostEvent(event.NewHeroStar(p.playerID, oldStar, starHero.Star))
  119. }
  120. heroTable.Save2Queue()
  121. p.Response(session, starHero.ToProto())
  122. // 同步更新到redis
  123. hero.Service().SyncUpdateHeroToRedis(p.playerID, starHero)
  124. // 推送变更信息
  125. hero.Service().ChangePush(p.playerID, req.RemoveHeroGUIDs, starHero)
  126. }
  127. // isHeroStarViable 判断是否可以升星
  128. func (p *actorPlayer) isHeroStarViable(hero *dbHero.Hero) int32 {
  129. // 已经最大星级
  130. if hero.Star >= data.Const.HeroMaxStar {
  131. return code.HeroStarLimit
  132. }
  133. // TODO 其它条件: 如果是否在队伍中, 是否锁定....
  134. return code.OK
  135. }
  136. // isHeroStarMatch 判断升星材料是否匹配
  137. func (p *actorPlayer) isHeroStarMatch(starHero, removeHero *dbHero.Hero) int32 {
  138. // 不能吃自己
  139. if starHero.GUID == removeHero.GUID {
  140. return code.HeroStarMismatch
  141. }
  142. // 升星需要同样的英雄
  143. if removeHero.HeroID != starHero.HeroID {
  144. return code.HeroStarMismatch
  145. }
  146. // TODO 其它条件: 如果是否在队伍中, 是否锁定....
  147. // TODO 暂时屏蔽 后面callwait去map判断或者在redis中判断
  148. // if removeHero.IsAlreadyTeamUp() {
  149. // return code.TeamHeroAlreadyUp
  150. // }
  151. return code.OK
  152. }
  153. func (*actorPlayer) heroLordLevelUp(eventData cfacade.IEventData) {
  154. e, ok := eventData.(*event.LordLevelUp)
  155. if !ok {
  156. return
  157. }
  158. var (
  159. playerID = e.UniqueID()
  160. playerTable, _ = db.GetPlayerTable(playerID)
  161. heroTable = db.GetHeroTable(playerID)
  162. )
  163. for _, hero := range heroTable.Heroes {
  164. // 更新英雄等级(冗余)
  165. hero.Level = playerTable.Lord.Level
  166. // 更新英雄属性
  167. gameFacade.Hero.UpdateAttrs(playerID, hero)
  168. }
  169. heroTable.Save2Queue()
  170. // 同步更新到redis
  171. // TODO 全量更新优化
  172. hero.Service().SyncUpdateHeroToRedis(playerID, heroTable.Heroes.GetAll()...)
  173. // TODO FIXME 英雄太多全部推送数据太大
  174. // TODO
  175. // TODO
  176. // gameFacade.Hero.Push(playerID)
  177. }
  178. // heroSkillUp 英雄技能升级
  179. func (p *actorPlayer) heroSkillUp(session *cproto.Session, req *pb.I64I32) {
  180. var (
  181. heroTable = p.HeroTable()
  182. heroGUID = req.Key
  183. skillID = req.Value
  184. )
  185. heroEntity, found := heroTable.Heroes.Get(heroGUID)
  186. if !found {
  187. p.ResponseCode(session, code.HeroNotExist)
  188. return
  189. }
  190. // 判断当前技能是否已解锁
  191. skillLevel := heroEntity.GetSkillLevel(skillID)
  192. if skillLevel < 1 {
  193. p.ResponseCode(session, code.SkillNotUnlock)
  194. return
  195. }
  196. skillRow, found := data.Skill.GetByID(skillID)
  197. if !found {
  198. p.ResponseCode(session, code.SkillNotExists)
  199. return
  200. }
  201. // 判断是否达到最大等级
  202. if skillLevel >= skillRow.MaxLv {
  203. p.ResponseCode(session, code.SkillLevelMax)
  204. return
  205. }
  206. // 获取当前技能等级配置
  207. skillLevelRow, found := data.SkillLevel.GetBySkillIDLevel(skillID, skillLevel)
  208. if !found {
  209. p.ResponseCode(session, code.ConfigNotExist_SkillLevel)
  210. return
  211. }
  212. // 消耗为空表示升到了最高级
  213. if skillLevelRow.UpgradeCost.IsEmpty() {
  214. p.ResponseCode(session, code.SkillUpCostError)
  215. return
  216. }
  217. // 判断下一级配置是否存在
  218. nextSkillLevelRow, found := data.SkillLevel.GetBySkillIDLevel(skillID, skillLevel+1)
  219. if !found {
  220. p.ResponseCode(session, code.ConfigNotExist_SkillLevel)
  221. return
  222. }
  223. // 升级星级不足
  224. if heroEntity.Star < nextSkillLevelRow.UnlockStar {
  225. p.ResponseCode(session, code.SkillLevelLimit)
  226. return
  227. }
  228. // 消耗资源
  229. _, errCode := asset.Service().Subs(p.playerID, skillLevelRow.UpgradeCost, ao.HeroSkillLevelUpCost)
  230. if code.IsFail(errCode) {
  231. p.ResponseCode(session, errCode)
  232. return
  233. }
  234. // 升级
  235. skillLevel++
  236. heroEntity.SetSkillLevel(skillID, skillLevel)
  237. heroTable.Save2Queue()
  238. rsp := &pb.HeroSkillResponse{
  239. HeroGUID: heroGUID,
  240. HeroSkillID: skillID,
  241. HeroSkillLevel: skillLevel,
  242. }
  243. p.Response(session, rsp)
  244. // 同步更新到redis
  245. hero.Service().SyncUpdateHeroToRedis(p.playerID, heroEntity)
  246. }
  247. // 英雄穿戴装备
  248. func (p *actorPlayer) heroWearEquip(session *cproto.Session, req *pb.HeroEquip) {
  249. var (
  250. heroTable = p.HeroTable()
  251. equipTable = p.EquipTable()
  252. heroGUID = req.HeroGUID // 英雄GUID
  253. equipGUIDList = req.EquipGUIDList // 装备GUID列表
  254. )
  255. if heroGUID < 1 ||
  256. len(equipGUIDList) > constant.HeroEquipSlotMax ||
  257. len(equipGUIDList) < constant.HeroEquipSlotMin {
  258. p.ResponseCode(session, code.IllegalArgument)
  259. return
  260. }
  261. // 穿戴装备的英雄非待命状态
  262. errCode := p.checkHeroCanUpdateEquip(heroGUID)
  263. if code.IsFail(errCode) {
  264. p.ResponseCode(session, errCode)
  265. return
  266. }
  267. weHero, found := heroTable.Heroes.Get(heroGUID)
  268. if !found {
  269. p.ResponseCode(session, code.HeroNotExist)
  270. return
  271. }
  272. var (
  273. validEquipList []*dbEquip.Equip // 过滤可穿戴装备
  274. replaceEquipList []*dbEquip.Equip // 需要先脱下的装备,包括当前英雄和被其他英雄穿戴的装备
  275. equipTypeList []enum.EquipType // 可穿戴的装备类型
  276. )
  277. for _, equipGUID := range equipGUIDList {
  278. equip, found := equipTable.Equips.Get(equipGUID)
  279. // 装备不存在
  280. if !found {
  281. continue
  282. }
  283. // 装备已穿戴当前英雄身上
  284. if weHero.EquipGUIDList.Contains(equipGUID) {
  285. continue
  286. }
  287. equipType := equip.GetEquipType()
  288. // 装备类型重复
  289. if slices.Contains(equipTypeList, equipType) {
  290. continue
  291. }
  292. // 记录需要脱掉装备在其他英雄身上的装备
  293. if equip.WearHeroGUID > 0 {
  294. // 卸下装备的英雄非待命状态
  295. errCode := p.checkHeroCanUpdateEquip(equip.WearHeroGUID)
  296. if code.IsFail(errCode) {
  297. p.ResponseCode(session, errCode)
  298. return
  299. }
  300. replaceEquipList = append(replaceEquipList, equip)
  301. }
  302. validEquipList = append(validEquipList, equip)
  303. equipTypeList = append(equipTypeList, equipType)
  304. }
  305. // 没有可穿戴的装备
  306. if len(validEquipList) < 1 {
  307. p.ResponseCode(session, code.OK)
  308. return
  309. }
  310. // 检查当前英雄身上需要被替换的装备
  311. for _, equipGUID := range weHero.EquipGUIDList {
  312. equip, _ := equipTable.Equips.Get(equipGUID)
  313. // 需要替换的装备先脱下
  314. if slices.Contains(equipTypeList, equip.GetEquipType()) {
  315. replaceEquipList = append(replaceEquipList, equip)
  316. }
  317. }
  318. // 先一键脱下装备
  319. changeHeroList := p.doTakeOffEquipList(heroTable, replaceEquipList)
  320. // 变更的装备
  321. var changeEquipList = types.NewList(replaceEquipList...)
  322. // 穿戴装备,双向绑定关系
  323. for _, equip := range validEquipList {
  324. weHero.EquipGUIDList.Add(equip.GUID)
  325. equip.WearHeroGUID = heroGUID
  326. // 记录变更的装备列表
  327. if !changeEquipList.Contains(equip) {
  328. changeEquipList.Add(equip)
  329. }
  330. }
  331. // 更新英雄属性
  332. gameFacade.Hero.UpdateAttrs(p.playerID, weHero)
  333. heroTable.Save2Queue()
  334. equipTable.Save2Queue()
  335. // 当前英雄也加入推送列表
  336. if !slices.Contains(changeHeroList, weHero) {
  337. changeHeroList = append(changeHeroList, weHero)
  338. }
  339. // 同步更新到redis
  340. hero.Service().SyncUpdateHeroToRedis(p.playerID, changeHeroList...)
  341. // 推送英雄变更
  342. hero.Service().ChangePush(p.playerID, nil, changeHeroList...)
  343. // 推送装备变更
  344. equip.Service().ChangePush(p.playerID, nil, changeEquipList...)
  345. p.ResponseCode(session, code.OK)
  346. // 发送英雄穿戴装备事件
  347. p.PostEvent(event.NewHeroWearEquip(p.playerID))
  348. }
  349. // 英雄脱下装备
  350. func (p *actorPlayer) heroTakeOffEquip(session *cproto.Session, req *pb.I64I64) {
  351. var (
  352. heroTable = p.HeroTable()
  353. equipTable = p.EquipTable()
  354. heroGUID = req.Key // 英雄GUID
  355. equipGUID = req.Value // 装备GUID
  356. )
  357. if heroGUID < 1 {
  358. p.ResponseCode(session, code.IllegalArgument)
  359. }
  360. // 检测英雄是否是出战状态
  361. errCode := p.checkHeroCanUpdateEquip(heroGUID)
  362. if code.IsFail(errCode) {
  363. p.ResponseCode(session, errCode)
  364. return
  365. }
  366. toeHero, found := heroTable.Heroes.Get(heroGUID)
  367. if !found {
  368. p.ResponseCode(session, code.HeroNotExist)
  369. return
  370. }
  371. if toeHero.EquipGUIDList.IsEmpty() {
  372. p.ResponseCode(session, code.HeroEquipNotWear)
  373. return
  374. }
  375. var validEquipList []*dbEquip.Equip // 可脱下的装备
  376. if equipGUID > 0 {
  377. equip, found := equipTable.Equips.Get(equipGUID)
  378. if !found {
  379. p.ResponseCode(session, code.EquipNotFound)
  380. return
  381. }
  382. // 装备是否穿戴在当前英雄身上
  383. if !toeHero.EquipGUIDList.Contains(equipGUID) {
  384. p.ResponseCode(session, code.HeroNotWearThisEquip)
  385. return
  386. }
  387. validEquipList = append(validEquipList, equip)
  388. } else {
  389. // 传0 一键脱下所有穿戴的装备
  390. for _, eguid := range toeHero.EquipGUIDList {
  391. equip, _ := equipTable.Equips.Get(eguid)
  392. validEquipList = append(validEquipList, equip)
  393. }
  394. }
  395. if len(validEquipList) < 1 {
  396. p.ResponseCode(session, code.HeroEquipNotWear)
  397. return
  398. }
  399. changeHeroList := p.doTakeOffEquipList(heroTable, validEquipList)
  400. heroTable.Save2Queue()
  401. equipTable.Save2Queue()
  402. // 同步更新到redis
  403. hero.Service().SyncUpdateHeroToRedis(p.playerID, changeHeroList...)
  404. // 推送英雄变更
  405. hero.Service().ChangePush(p.playerID, nil, changeHeroList...)
  406. // 推送装备变更
  407. equip.Service().ChangePush(p.playerID, nil, validEquipList...)
  408. p.ResponseCode(session, code.OK)
  409. }
  410. // 一键脱下装备,解除英雄与装备的穿戴关系。需要在上层保存
  411. //
  412. // wearEquipList 需要脱下的装备列表,上层过滤没有穿戴的装备
  413. func (p *actorPlayer) doTakeOffEquipList(heroTable *db.HeroTable, wearEquipList []*dbEquip.Equip) []*dbHero.Hero {
  414. var changeHeroList []*dbHero.Hero
  415. for _, equip := range wearEquipList {
  416. hero, _ := heroTable.Heroes.Get(equip.WearHeroGUID)
  417. /// 双向解除装备穿戴关系
  418. hero.RemoveEquip(equip.GUID)
  419. equip.WearHeroGUID = 0
  420. // 更新英雄属性
  421. gameFacade.Hero.UpdateAttrs(p.playerID, hero)
  422. // 记录需要推送变更的英雄列表
  423. changeHeroList = append(changeHeroList, hero)
  424. }
  425. return changeHeroList
  426. }
  427. // 检测英雄是否能穿戴装备 如果英雄处于出征状态是不能穿戴装备的
  428. func (p *actorPlayer) checkHeroCanUpdateEquip(heroGUIDs ...int64) int32 {
  429. if len(heroGUIDs) < 1 {
  430. return code.HeroNotExist
  431. }
  432. playerTable := p.PlayerTable()
  433. // 没有地图节点
  434. if len(playerTable.Map.MapNodeID) <= 0 {
  435. return code.OK
  436. }
  437. req := &pb.I64List{
  438. List: heroGUIDs,
  439. }
  440. // map检测英雄是否出征 如果出征中不能穿戴装备
  441. return p.mapPlayerCallWait(playerTable.Map.MapNodeID, nameRemote.MapPlayer_HeroCheckBusy, req)
  442. }