BaiZhanChengShenNS.lua 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417
  1. -- 百战成神(普通服 NS)
  2. --
  3. -- 玩法: 跨服 5v5 积分 PvP, 五族独立阵容 COMBAT_TYPE39~43, 5 局 3 胜。
  4. -- 开服>=45 天; 周六 0:10~周日 23:00; 每 21 天新轮。
  5. --
  6. -- 数据分工:
  7. -- 跨服 DB: 积分、排行、匹配池、玩家展示(REGISTER/UPDATE_SHOW)
  8. -- 本地 human.db.baiZhanChengShen: 免费次数、战报、crossRegistered
  9. -- human.bzcs_Match_Cache: 匹配列表/积分内存缓存(TTL 见 BZCS_MATCH_CACHE_TTL, 不持久化)
  10. -- CommonDB.KEY_BZCS_START_TIME: 本服活动轮次标记(与跨服 WL_BZCS_ACT_START 同步)
  11. --
  12. -- 跨服同步策略:
  13. -- 首次完成挑战 -> 先 LW_BZCS_REGISTER 再 LW_BZCS_FIGHT_END (crossRegistered=true, 每轮仅一次)
  14. -- 阵容/战力变更 -> LW_BZCS_UPDATE_SHOW (须已注册)
  15. -- 每场战斗结束 -> LW_BZCS_FIGHT_END (不重复 REGISTER)
  16. --
  17. -- 战斗:
  18. -- 挑战一次依次走 CG_COMBAT_BEGIN(COMBAT_TYPE39~43); 仅首场校验次数/道具并请求跨服
  19. -- 扣次在 C2N_CanFight; onFightEnd 链式 nextCombatType 打满 5 族或 3 胜, 整场结束发 fightReward
  20. -- 机器人: combatBegin; 真人: MiddleCommonLogic_CombatBegin_LW
  21. --
  22. -- 文件结构:
  23. -- 本地辅助 -> 客户端请求(BZCS_*) -> 跨服通知(C2N_*) -> 战斗相关 -> 对外接口
  24. local Config = require("Config")
  25. local Msg = require("core.Msg")
  26. local Lang = require("common.Lang")
  27. local Util = require("common.Util")
  28. local ObjHuman = require("core.ObjHuman")
  29. local Broadcast = require("broadcast.Broadcast")
  30. local CombatDefine = require("combat.CombatDefine")
  31. local CombatLogic = require("combat.CombatLogic")
  32. local CombatPosLogic = require("combat.CombatPosLogic")
  33. local RoleDBLogic = require("role.RoleDBLogic")
  34. local RoleHeadLogic = require("role.RoleHeadLogic")
  35. local RoleAttr = require("role.RoleAttr")
  36. local InnerMsg = require("core.InnerMsg")
  37. local MailManager = require("mail.MailManager")
  38. local MailExcel = require("excel.mail")
  39. local Timer = require("core.Timer")
  40. local Grid = require("bag.Grid")
  41. local BagLogic = require("bag.BagLogic")
  42. local CommonDB = require("common.CommonDB")
  43. local MiddleConnect = require("middle.MiddleConnect")
  44. local MiddleCommonLogic = require("middle.MiddleCommonLogic")
  45. local HeroExcel = require("excel.hero")
  46. local BaiZhanChengShenDefine = require("baiZhanChengShen.BaiZhanChengShenDefine")
  47. local BzcsLog = require("baiZhanChengShen.BaiZhanChengShenLog")
  48. local BzcsConfig = require("excel.baiZhanChengShen")
  49. local SkillExcel = require("excel.skill")
  50. local Skill = require("combat.Skill")
  51. local HeroLogic
  52. local MoshouLogic
  53. local ElfLogic
  54. local LOGTAG = "BaiZhanChengShen"
  55. -- 战斗类型 -> 种族 camp, 用于 checkUpdatePos 限制上阵
  56. local COMBATTYPE_2_CAMP = {
  57. [CombatDefine.COMBAT_TYPE39] = 1,
  58. [CombatDefine.COMBAT_TYPE40] = 2,
  59. [CombatDefine.COMBAT_TYPE41] = 3,
  60. [CombatDefine.COMBAT_TYPE42] = 4,
  61. [CombatDefine.COMBAT_TYPE43] = 5,
  62. }
  63. ------------------------------------ 本地辅助 ------------------------------------
  64. -- 逻辑服与跨服链路是否可用(WL_HELLO 成功后为 true)
  65. local function isMiddleReady()
  66. if _G.is_middle then return true end
  67. return MiddleConnect.IS_MIDDLE_CONNECT == true
  68. end
  69. -- 发送 LW 到跨服; 断连时提示玩家并返回 false
  70. local function sendLwToMiddle(human, msgData)
  71. if not isMiddleReady() then
  72. if human and human.fd then
  73. Broadcast.sendErr(human, Lang.MIDDLE_SVR_ERR_CONNECT)
  74. end
  75. return false
  76. end
  77. InnerMsg.sendMsg(0, msgData)
  78. return true
  79. end
  80. -- 当前星期(Util.getWeekDay: 1=周日, 7=周六)
  81. local function getWDay()
  82. return Util.getWeekDay()
  83. end
  84. -- 开服天数是否满足参与条件
  85. local function isServerEligible()
  86. return CommonDB.getServerOpenDay() >= BaiZhanChengShenDefine.BZCS_OPEN_SVR_DAY
  87. end
  88. -- 本服是否在活动期(跨服已广播 KEY_BZCS_START_TIME 且 IsRunning)
  89. local function actStartTimeCheck()
  90. local wDay = getWDay()
  91. if wDay > BaiZhanChengShenDefine.BZCS_OPEN_WDAY_AREA[2]
  92. and wDay < BaiZhanChengShenDefine.BZCS_OPEN_WDAY_AREA[1] then
  93. return false
  94. end
  95. local startTime = CommonDB.getValueByKey(CommonDB.KEY_BZCS_START_TIME)
  96. if not startTime or startTime == 0 then
  97. return false
  98. end
  99. return IsRunning(startTime)
  100. end
  101. -- 新活动轮次时清空本地战报
  102. local function clearRoundLocalData(db)
  103. db.warReport = {}
  104. db.crossRegistered = false
  105. end
  106. -- 清除匹配内存缓存(积分变化/新轮/强制刷新)
  107. local function clearMatchCache(human)
  108. if human then
  109. human.bzcs_Match_Cache = nil
  110. end
  111. end
  112. -- 活动开始时间变化(新轮开启/结束)时重置本地轮次数据
  113. local function resetRoundLocalIfNeeded(human, db)
  114. local actStart = CommonDB.getValueByKey(CommonDB.KEY_BZCS_START_TIME) or 0
  115. if (db.actStartTime or 0) ~= actStart then
  116. db.actStartTime = actStart
  117. clearRoundLocalData(db)
  118. clearMatchCache(human)
  119. end
  120. end
  121. -- 玩家本地百战成神数据 (不入跨服 Mongo)
  122. -- freeTimes 今日剩余免费挑战次数(跨天由 updateDaily 重置)
  123. -- actStartTime 已同步的活动轮开始时间(与 KEY_BZCS_START_TIME 对比清轮次数据)
  124. -- crossRegistered 本轮是否已向跨服 REGISTER
  125. -- warReport 本地战报 {warType,...}, 按时间尾插(最老在[1]), 下发时倒序为新在前
  126. local function getBzcsDb(human)
  127. human.db.baiZhanChengShen = human.db.baiZhanChengShen or {
  128. freeTimes = BaiZhanChengShenDefine.BZCS_FREE_TIMES,
  129. actStartTime = 0,
  130. crossRegistered = false,
  131. warReport = {},
  132. }
  133. resetRoundLocalIfNeeded(human, human.db.baiZhanChengShen)
  134. return human.db.baiZhanChengShen
  135. end
  136. local function getMatchCache(human)
  137. return human and human.bzcs_Match_Cache
  138. end
  139. local function setMatchCache(human, myScore, opponentList, myRank)
  140. human.bzcs_Match_Cache = {
  141. myScore = myScore,
  142. myRank = myRank or 0,
  143. matchScore = myScore,
  144. opponentList = opponentList or {},
  145. cacheTime = os.time(),
  146. }
  147. end
  148. -- 展示刷新: 保留 matchScore(匹配基准), 更新对手展示并重置 TTL
  149. local function refreshMatchCacheDisplay(human, myScore, opponentList, myRank)
  150. local cache = getMatchCache(human)
  151. if myRank == nil then
  152. myRank = cache and cache.myRank or 0
  153. end
  154. human.bzcs_Match_Cache = {
  155. myScore = myScore,
  156. myRank = myRank,
  157. matchScore = cache and cache.matchScore or myScore,
  158. opponentList = opponentList or {},
  159. cacheTime = os.time(),
  160. }
  161. end
  162. -- 积分变动: 清空对手列表, 重置 matchScore/TTL(从变动时刻重新计时)
  163. local function resetMatchCacheOnScoreChange(human, newScore)
  164. if not human then return end
  165. newScore = newScore or BaiZhanChengShenDefine.BZCS_INIT_SCORE
  166. human.bzcs_Match_Cache = {
  167. myScore = newScore,
  168. myRank = 0,
  169. matchScore = newScore,
  170. opponentList = {},
  171. cacheTime = os.time(),
  172. }
  173. end
  174. local function isMatchCacheFresh(human)
  175. local cache = getMatchCache(human)
  176. if not cache or not cache.opponentList or not next(cache.opponentList) then
  177. return false
  178. end
  179. if (cache.myScore or 0) ~= (cache.matchScore or 0) then
  180. return false
  181. end
  182. local ttl = BaiZhanChengShenDefine.BZCS_MATCH_CACHE_TTL or 30
  183. return (os.time() - (cache.cacheTime or 0)) < ttl
  184. end
  185. local function collectRefreshRanks(opponentList)
  186. local ranks = {}
  187. for _, opp in ipairs(opponentList or {}) do
  188. if opp.rank and opp.rank > 0 then
  189. ranks[#ranks + 1] = opp.rank
  190. end
  191. end
  192. return ranks
  193. end
  194. -- 校验 rank 是否属于当前匹配缓存(全服名次, 非列表下标 1~3)
  195. local function resolveMatchOpponentByRank(human, rank)
  196. if not rank or rank < 1 then
  197. return nil
  198. end
  199. local cache = getMatchCache(human)
  200. for _, opp in ipairs(cache and cache.opponentList or {}) do
  201. if opp.rank == rank and opp.uuid then
  202. return opp
  203. end
  204. end
  205. return nil
  206. end
  207. -- 指定种族阵容是否至少上阵 1 名英雄(空位 "0"/"" 不算)
  208. local function hasRaceLineupHero(human, race)
  209. local combatType = BaiZhanChengShenDefine.BZCS_RACE_COMBAT_TYPE[race]
  210. if not combatType then return false end
  211. local heroList = CombatPosLogic.getCombatHeros(human, combatType)
  212. if not heroList then return false end
  213. for _, heroUuid in pairs(heroList) do
  214. if heroUuid and heroUuid ~= "" and heroUuid ~= "0" then
  215. return true
  216. end
  217. end
  218. return false
  219. end
  220. -- 五族阵容是否均已至少上阵 1 名英雄(供 GC_BZCS_MATCH_LIST.allLineupReady)
  221. local function isAllRaceLineupReady(human)
  222. for _, race in ipairs(BaiZhanChengShenDefine.BZCS_RACE_ORDER) do
  223. if not hasRaceLineupHero(human, race) then
  224. return false
  225. end
  226. end
  227. return true
  228. end
  229. -- 挑战前校验五族阵容均已上阵英雄
  230. local function checkAllRaceLineupForFight(human)
  231. for _, race in ipairs(BaiZhanChengShenDefine.BZCS_RACE_ORDER) do
  232. if not hasRaceLineupHero(human, race) then
  233. local raceName = BaiZhanChengShenDefine.BZCS_RACE_NAME[race] or ""
  234. Broadcast.sendErr(human, Util.format(Lang.BZCS_NEED_RACE_LINEUP, raceName))
  235. return false
  236. end
  237. end
  238. return true
  239. end
  240. -- 组装并下发 GC_BZCS_MATCH_LIST
  241. local function sendMatchListGC(human, myScore, opponentList, myRank)
  242. local db = getBzcsDb(human)
  243. local msgRet = Msg.gc.GC_BZCS_MATCH_LIST
  244. msgRet.myRank = myRank or 0
  245. msgRet.myScore = myScore or BaiZhanChengShenDefine.BZCS_INIT_SCORE
  246. msgRet.freeTimes = db.freeTimes or 0
  247. Grid.makeItem(
  248. msgRet.ticketCost,
  249. BaiZhanChengShenDefine.BZCS_TICKET_ITEM_ID,
  250. BaiZhanChengShenDefine.BZCS_TICKET_COST
  251. )
  252. msgRet.allLineupReady = isAllRaceLineupReady(human) and 1 or 0
  253. msgRet.opponentList[0] = #(opponentList or {})
  254. for i, opp in ipairs(opponentList or {}) do
  255. local net = msgRet.opponentList[i]
  256. net.rank = opp.rank or 0
  257. net.name = opp.name
  258. net.body = opp.body or 0
  259. net.power = opp.power
  260. net.score = opp.score
  261. net.serverId = BaiZhanChengShenDefine.ToClientServerId(opp.serverId, opp.isRobot)
  262. end
  263. Msg.send(msgRet, human.fd)
  264. end
  265. -- 单个英雄展示字段(阵容/协议用)
  266. local function getHeroInfo(human, heroUuid)
  267. HeroLogic = HeroLogic or require("hero.HeroLogic")
  268. local heroGrid = HeroLogic.getHeroGridByUuid(human, heroUuid)
  269. if not heroGrid then return end
  270. local heroCfg = HeroExcel.hero[heroGrid.id]
  271. RoleAttr.calcHeroGrid(heroGrid, nil, human)
  272. return {
  273. heroUuid = heroUuid,
  274. heroStar = heroGrid.star,
  275. heroLevel = heroGrid.lv,
  276. heroCamp = heroGrid.camp or heroCfg.camp,
  277. heroBody = heroCfg.body,
  278. heroIcon = heroGrid.head or heroCfg.head,
  279. heroPower = heroGrid.zhandouli,
  280. heroId = heroGrid.id,
  281. heroQuality = heroCfg.grade,
  282. }
  283. end
  284. -- 某战斗类型阵容内已上阵英雄展示列表
  285. local function getRaceHeroListInfo(human, combatType)
  286. local heroArr = {}
  287. local len = 0
  288. local heroList = CombatPosLogic.getCombatHeros(human, combatType)
  289. if not heroList then
  290. return heroArr
  291. end
  292. for _, heroUuid in pairs(heroList) do
  293. if heroUuid ~= "" and heroUuid ~= "0" then
  294. local heroInfo = getHeroInfo(human, heroUuid)
  295. if heroInfo then
  296. len = len + 1
  297. heroArr[len] = heroInfo
  298. end
  299. end
  300. end
  301. return heroArr
  302. end
  303. -- 技能 icon(助阵凤凰 moshouSkill 用)
  304. local function getBzcsSkillIcon(skillId)
  305. if not skillId or skillId < 1 then
  306. return ""
  307. end
  308. local skillConfig = SkillExcel.skill[skillId] or Skill.GetSkillConfig(skillId)
  309. return skillConfig and skillConfig.icon or ""
  310. end
  311. -- 填充精灵位展示(同 CombatPosLogic.Elf_Pos_Query)
  312. local function fillBzcsElfPosArr(msgElfPosArr, elfList, elfSkillIds, human)
  313. ElfLogic = ElfLogic or require("elf.ElfLogic")
  314. msgElfPosArr[0] = CombatDefine.COMBAT_ELF_NOW_CNT
  315. for i = 1, CombatDefine.COMBAT_ELF_NOW_CNT do
  316. local net = msgElfPosArr[i]
  317. net.elfId = 0
  318. net.nowSkillDesc = ""
  319. net.nowSkillIcon = ""
  320. local elfId = elfList and elfList[i]
  321. if elfId and elfId > 0 then
  322. local skillId
  323. if human then
  324. skillId = ElfLogic.GetElfSkill(human, elfId)
  325. else
  326. skillId = elfSkillIds and elfSkillIds[i]
  327. end
  328. if skillId and skillId > 0 then
  329. local skillConfig = Skill.GetSkillConfig(skillId)
  330. if skillConfig then
  331. net.elfId = elfId
  332. net.nowSkillDesc = skillConfig.desc
  333. net.nowSkillIcon = skillConfig.icon
  334. end
  335. end
  336. end
  337. end
  338. end
  339. -- 填充 BZCS_TEAM_INFO(英雄/助阵 icon/精灵展示)
  340. local function fillBzcsTeamNet(teamNet, show, human, combatType)
  341. show = BaiZhanChengShenDefine.ExpandBzcsRaceShow(show) or show
  342. local heroList = show.heroArr or {}
  343. teamNet.power = BaiZhanChengShenDefine.CalcRaceTeamPower(show)
  344. teamNet.formation = show.formation or 1
  345. teamNet.heroArr[0] = #heroList
  346. for j, h in ipairs(heroList) do
  347. local net = teamNet.heroArr[j]
  348. net.heroBody = h.heroBody or 0
  349. net.heroStar = h.heroStar or 0
  350. net.heroLv = h.heroLevel or h.heroLv or 0
  351. net.heroCamp = h.heroCamp or 0
  352. net.heroIcon = h.heroIcon or 0
  353. net.heroId = h.heroId or 0
  354. net.heroQuality = h.heroQuality or 1
  355. end
  356. local helpSkillId = show.helpSkillId or 0
  357. if human and combatType and helpSkillId < 1 then
  358. MoshouLogic = MoshouLogic or require("moshou.MoshouLogic")
  359. local combatHeroDB = CombatPosLogic.getCombatHeroDB(human, combatType)
  360. if combatHeroDB and combatHeroDB.helpList then
  361. helpSkillId = MoshouLogic.getPutMoshouSkillID(human, combatType, combatHeroDB.helpList) or 0
  362. end
  363. end
  364. teamNet.moshouSkill = getBzcsSkillIcon(helpSkillId)
  365. fillBzcsElfPosArr(teamNet.elfPosArr, show.elfList, show.elfSkillIds, human)
  366. end
  367. -- 单种族阵容展示(跨服仅存 heroArr/阵法/助阵技能id/精灵id+技能id; 战力见各英雄 heroPower)
  368. local function getRaceLineupShow(human, race)
  369. local combatType = BaiZhanChengShenDefine.BZCS_RACE_COMBAT_TYPE[race]
  370. local heroArr = getRaceHeroListInfo(human, combatType)
  371. local combatHeroDB = CombatPosLogic.getCombatHeroDB(human, combatType)
  372. local helpSkillId = 0
  373. local elfList, elfSkillIds
  374. if combatHeroDB then
  375. MoshouLogic = MoshouLogic or require("moshou.MoshouLogic")
  376. ElfLogic = ElfLogic or require("elf.ElfLogic")
  377. if combatHeroDB.helpList then
  378. helpSkillId = MoshouLogic.getPutMoshouSkillID(human, combatType, combatHeroDB.helpList) or 0
  379. end
  380. if combatHeroDB.elfList then
  381. elfList = {}
  382. elfSkillIds = {}
  383. for i = 1, CombatDefine.COMBAT_ELF_NOW_CNT do
  384. local elfId = combatHeroDB.elfList[i]
  385. if elfId and elfId > 0 then
  386. elfList[i] = elfId
  387. elfSkillIds[i] = ElfLogic.GetElfSkill(human, elfId) or 0
  388. end
  389. end
  390. end
  391. end
  392. return {
  393. heroArr = heroArr,
  394. formation = combatHeroDB and combatHeroDB.formation or 1,
  395. helpSkillId = helpSkillId,
  396. elfList = elfList,
  397. elfSkillIds = elfSkillIds,
  398. }
  399. end
  400. -- 跨服展示包: 外观 + 五族阵容(存入 DB.playerList[].showInfo, 仅 REGISTER 全量使用)
  401. local function buildShowInfo(human)
  402. local heroArr = {}
  403. for _, race in ipairs(BaiZhanChengShenDefine.BZCS_RACE_ORDER) do
  404. heroArr[race] = getRaceLineupShow(human, race)
  405. end
  406. return {
  407. name = human.db.name,
  408. head = RoleHeadLogic.getRoleAppearance(human, RoleHeadLogic.HEAD_TYPE_1),
  409. headFrame = RoleHeadLogic.getRoleAppearance(human, RoleHeadLogic.HEAD_TYPE_2),
  410. body = RoleHeadLogic.getRoleAppearance(human, RoleHeadLogic.HEAD_TYPE_3),
  411. heroArr = heroArr,
  412. }
  413. end
  414. -- 按 updateType 组装增量 showInfo(仅含本次变更字段)
  415. local function buildShowPatch(human, updateType, race)
  416. if updateType == BaiZhanChengShenDefine.BZCS_UPDATE_SHOW_NAME then
  417. return {name = human.db.name}
  418. elseif updateType == BaiZhanChengShenDefine.BZCS_UPDATE_SHOW_HEAD then
  419. return {head = RoleHeadLogic.getRoleAppearance(human, RoleHeadLogic.HEAD_TYPE_1)}
  420. elseif updateType == BaiZhanChengShenDefine.BZCS_UPDATE_SHOW_HEAD_FRAME then
  421. return {headFrame = RoleHeadLogic.getRoleAppearance(human, RoleHeadLogic.HEAD_TYPE_2)}
  422. elseif updateType == BaiZhanChengShenDefine.BZCS_UPDATE_SHOW_BODY then
  423. return {body = RoleHeadLogic.getRoleAppearance(human, RoleHeadLogic.HEAD_TYPE_3)}
  424. elseif updateType == BaiZhanChengShenDefine.BZCS_UPDATE_SHOW_LINEUP then
  425. if not race then return end
  426. return {heroArr = {[race] = getRaceLineupShow(human, race)}}
  427. end
  428. end
  429. -- 按排名取周期奖励道具配置
  430. local function getRankReward(rank)
  431. for _, v in ipairs(BzcsConfig.rankReward) do
  432. if rank >= v.rankArea[1] and rank <= v.rankArea[2] then
  433. return v.awardList
  434. end
  435. end
  436. end
  437. -- 道具列表转日志串 itemId:cnt,...
  438. local function formatAwardList(awardList)
  439. if not awardList or #awardList < 1 then
  440. return ""
  441. end
  442. local parts = {}
  443. for i, item in ipairs(awardList) do
  444. parts[i] = string.format("%s:%s", item[1] or 0, item[2] or 0)
  445. end
  446. return table.concat(parts, ",")
  447. end
  448. -- 整场挑战结束发放道具奖励(读 excel fightReward: 1=胜 2=负)
  449. -- combatInfo.rewardItem 供 GC_COMBAT_FINISH 展示, 参考 BattleLogic.onFightEnd
  450. local function grantFightReward(human, atkWin, combatInfo)
  451. if not human or not human.db then return end
  452. local rewardId = atkWin and BaiZhanChengShenDefine.BZCS_FIGHT_REWARD_WIN_ID
  453. or BaiZhanChengShenDefine.BZCS_FIGHT_REWARD_LOSE_ID
  454. local fightReward = BzcsConfig.fightReward
  455. local cfg = fightReward and fightReward[rewardId]
  456. if not cfg or not cfg.awardList or not next(cfg.awardList) then
  457. BzcsLog.logAction("fight_reward_miss", string.format(
  458. "uuid=%s atkWin=%s rewardId=%s", human.db._id, atkWin and 1 or 0, rewardId
  459. ))
  460. return
  461. end
  462. if combatInfo then
  463. combatInfo.rewardItem = {}
  464. end
  465. local awardList = {}
  466. for i, itemInfo in ipairs(cfg.awardList) do
  467. local itemID = itemInfo[1]
  468. local itemCnt = itemInfo[2]
  469. awardList[i] = {itemID, itemCnt}
  470. if combatInfo then
  471. combatInfo.rewardItem[i] = {itemID, itemCnt}
  472. end
  473. BagLogic.addItem(human, itemID, itemCnt, LOGTAG)
  474. end
  475. BzcsLog.logAction("fight_reward", string.format(
  476. "uuid=%s atkWin=%s rewardId=%s itemCnt=%s items=%s",
  477. human.db._id, atkWin and 1 or 0, rewardId, #awardList, formatAwardList(awardList)
  478. ))
  479. end
  480. -- 发周期结算邮件(排名写入正文)
  481. local function sendMail(mailId, receiverUuid, itemArray, rank)
  482. local mailCfg = MailExcel.mail[mailId]
  483. if not mailCfg then
  484. BzcsLog.logAction("reward_mail_miss", string.format(
  485. "uuid=%s rank=%s mailId=%s", receiverUuid or "", rank or 0, mailId or 0
  486. ))
  487. return
  488. end
  489. if not itemArray or not next(itemArray) then
  490. BzcsLog.logAction("reward_mail_empty", string.format(
  491. "uuid=%s rank=%s mailId=%s", receiverUuid or "", rank or 0, mailId
  492. ))
  493. return
  494. end
  495. local content = mailCfg.content
  496. if rank then
  497. if rank > BaiZhanChengShenDefine.BZCS_RANK_MAX then
  498. rank = "100+"
  499. end
  500. content = Util.format(content, rank)
  501. end
  502. MailManager.add(MailManager.SYSTEM, receiverUuid, mailCfg.title, content, itemArray, mailCfg.senderName or "GM")
  503. BzcsLog.logAction("reward_mail_ok", string.format(
  504. "uuid=%s rank=%s mailId=%s itemCnt=%s items=%s",
  505. receiverUuid, rank or 0, mailId, #itemArray, formatAwardList(itemArray)
  506. ))
  507. end
  508. -- 批量发奖队列(分批发邮件, 失败重试)
  509. local function createRewardQueue()
  510. local q = {playerArray = {}, insertMaxNum = 100, repeatMaxTimes = 3, repeatTb = {}}
  511. function q:add(info) table.insert(self.playerArray, info) end
  512. function q:insertDB()
  513. local maxNum = math.min(self.insertMaxNum, #self.playerArray)
  514. for _ = 1, maxNum do
  515. local info = table.remove(self.playerArray)
  516. local uuid, rank = info[1], info[2]
  517. local itemArray = getRankReward(rank)
  518. if not itemArray then
  519. BzcsLog.logAction("reward_cfg_miss", string.format("uuid=%s rank=%s", uuid or "", rank or 0))
  520. else
  521. local ok, err = pcall(sendMail, BaiZhanChengShenDefine.BZCS_AWARD_MAIL_ID, uuid, itemArray, rank)
  522. if not ok then
  523. local retry = (self.repeatTb[uuid] or 0) + 1
  524. if retry <= self.repeatMaxTimes then
  525. self.repeatTb[uuid] = retry
  526. q:add(info)
  527. BzcsLog.logAction("reward_mail_fail", string.format(
  528. "uuid=%s rank=%s retry=%s err=%s", uuid, rank, retry, err
  529. ))
  530. else
  531. BzcsLog.logAction("reward_mail_giveup", string.format(
  532. "uuid=%s rank=%s retry=%s err=%s", uuid, rank, retry, err
  533. ))
  534. end
  535. end
  536. end
  537. end
  538. if #self.playerArray > 0 then
  539. Timer.addLater(3, self.insertDB, self)
  540. end
  541. end
  542. return q
  543. end
  544. -- bzcs_Battle_Cache: defRank/defUuid/defServerId/defName/defScore/isRobot, raceIdx, atkW/defW, rounds
  545. -- 战报尾插; 超上限删最老; 下发 GC 时倒序
  546. local function addWarReport(human, report)
  547. local db = getBzcsDb(human)
  548. db.warReport = db.warReport or {}
  549. db.warReport[#db.warReport + 1] = report
  550. if #db.warReport > BaiZhanChengShenDefine.BZCS_WARREPORT_MAX then
  551. table.remove(db.warReport, 1)
  552. end
  553. end
  554. -- 是否仍有挑战次数(免费或道具115); 仅校验不扣
  555. local function canChallenge(human)
  556. local db = getBzcsDb(human)
  557. if db.freeTimes > 0 then
  558. return true
  559. end
  560. return BagLogic.getItemCnt(human, BaiZhanChengShenDefine.BZCS_TICKET_ITEM_ID) >= BaiZhanChengShenDefine.BZCS_TICKET_COST
  561. end
  562. -- 优先扣免费次数, 否则扣门票; 整场仅在 C2N_CanFight 扣 1 次
  563. local function deductChallengeTimes(human)
  564. local db = getBzcsDb(human)
  565. if db.freeTimes > 0 then
  566. db.freeTimes = db.freeTimes - 1
  567. return true
  568. end
  569. if BagLogic.getItemCnt(human, BaiZhanChengShenDefine.BZCS_TICKET_ITEM_ID) < BaiZhanChengShenDefine.BZCS_TICKET_COST then
  570. return false
  571. end
  572. BagLogic.delItem(human, BaiZhanChengShenDefine.BZCS_TICKET_ITEM_ID, BaiZhanChengShenDefine.BZCS_TICKET_COST, LOGTAG)
  573. return true
  574. end
  575. -- 本轮首次完成挑战时向跨服注册, 每轮仅一次
  576. local function tryRegisterToCross(human)
  577. local db = getBzcsDb(human)
  578. if db.crossRegistered then
  579. return
  580. end
  581. local reg = InnerMsg.lw.LW_BZCS_REGISTER
  582. reg.playerInfo = {
  583. uuid = human.db._id,
  584. serverId = Config.SVR_INDEX,
  585. firstJoinTime = os.time(),
  586. showInfo = buildShowInfo(human),
  587. }
  588. InnerMsg.sendMsg(0, reg)
  589. BzcsLog.logAction("register_send", string.format("uuid=%s serverId=%s", human.db._id, Config.SVR_INDEX))
  590. end
  591. -- 按 raceIdx 开打当前族; 机器人本地战, 真人跨服
  592. local function startRaceCombat(human, cache)
  593. local raceIdx = cache.raceIdx
  594. local combatType = BaiZhanChengShenDefine.BZCS_RACE_COMBAT_TYPE[raceIdx]
  595. if cache.isRobot == 1 then
  596. CombatLogic.combatBegin(human, nil, {cache.defUuid, raceIdx}, combatType)
  597. else
  598. MiddleCommonLogic.MiddleCommonLogic_CombatBegin_LW(human, {
  599. combatType = combatType,
  600. nServerIndex = cache.defServerId,
  601. param = cache.defUuid,
  602. })
  603. end
  604. end
  605. -- 5 局系列结束: 注册跨服(首次) -> LW_BZCS_FIGHT_END; 战报待 WL 回包后写入
  606. local function challengeFinish(human, cache)
  607. if not isMiddleReady() then
  608. BzcsLog.logAction("fight_end_middle_down", string.format("uuid=%s def=%s", human.db._id, cache.defUuid or ""))
  609. if human.fd then
  610. Broadcast.sendErr(human, Lang.MIDDLE_SVR_ERR_CONNECT)
  611. end
  612. human.bzcs_Battle_Cache = nil
  613. return
  614. end
  615. -- 须先于 FIGHT_END: 跨服 UpdateScore 依赖 playerList 中已有攻方
  616. tryRegisterToCross(human)
  617. local atkWin = cache.atkW >= BaiZhanChengShenDefine.BZCS_WIN_TARGET
  618. local msgData = InnerMsg.lw.LW_BZCS_FIGHT_END
  619. msgData.atkUuid = human.db._id
  620. msgData.atkServerId = Config.SVR_INDEX
  621. msgData.atkName = human.db.name
  622. msgData.defUuid = cache.defUuid
  623. msgData.defName = cache.defName
  624. msgData.defServerId = cache.defServerId
  625. msgData.atkWin = atkWin and 1 or 0
  626. msgData.raceResults = cache.rounds
  627. InnerMsg.sendMsg(0, msgData)
  628. BzcsLog.logAction("fight_end_send", string.format(
  629. "atk=%s def=%s atkWin=%s isRobot=%s atkW=%s defW=%s",
  630. human.db._id, cache.defUuid or "", msgData.atkWin, cache.isRobot or 0, cache.atkW, cache.defW
  631. ))
  632. human.bzcs_Battle_Cache = nil
  633. end
  634. ------------------------------------ 客户端请求 (Handler.lua -> BZCS_*) ------------------------------------
  635. -- 由 Handler.lua 转发; 需跨服的走 sendLwToMiddle, 仅本地数据的直接 GC
  636. -- 请求跨服匹配列表; forceRefresh=true 时全量重匹配
  637. local function requestMatchList(human, forceRefresh)
  638. if human.fd then
  639. ObjHuman.updateDaily(human)
  640. end
  641. getBzcsDb(human)
  642. if not isServerEligible() then
  643. return Broadcast.sendErr(human, Lang.COMMOM_NOT_ENABLED)
  644. end
  645. if not actStartTimeCheck() then
  646. return Broadcast.sendErr(human, Lang.COMMOM_NOT_ENABLED)
  647. end
  648. if forceRefresh then
  649. clearMatchCache(human)
  650. end
  651. if isMatchCacheFresh(human) then
  652. local cache = getMatchCache(human)
  653. return sendMatchListGC(human, cache.myScore, cache.opponentList, cache.myRank)
  654. end
  655. if not isMiddleReady() then
  656. return Broadcast.sendErr(human, Lang.MIDDLE_SVR_ERR_CONNECT)
  657. end
  658. local cache = getMatchCache(human)
  659. local msgData = InnerMsg.lw.LW_BZCS_MATCH
  660. msgData.sourceServerId = Config.SVR_INDEX
  661. msgData.playerUuid = human.db._id
  662. msgData.refreshRanks = {}
  663. if cache and cache.opponentList and next(cache.opponentList)
  664. and (cache.myScore or 0) == (cache.matchScore or 0) then
  665. msgData.refreshRanks = collectRefreshRanks(cache.opponentList)
  666. end
  667. if not sendLwToMiddle(human, msgData) then
  668. return
  669. end
  670. end
  671. -- 匹配主界面: 下发 myScore + 3 名对手(score/name/power)
  672. -- 内存缓存未过期直返; 过期则跨服按 rank 刷新展示; 无缓存则全量匹配
  673. function BZCS_MatchList(human)
  674. requestMatchList(human, false)
  675. end
  676. -- 客户端刷新匹配对手(清缓存后跨服按积分±500步进重匹配3人, 回包 GC_BZCS_MATCH_LIST)
  677. function BZCS_MatchRefresh(human)
  678. if not actStartTimeCheck() then
  679. return Broadcast.sendErr(human, Lang.COMMOM_NOT_ENABLED)
  680. end
  681. if human.bzcs_Battle_Cache then
  682. return Broadcast.sendErr(human, Lang.DATA_ERR)
  683. end
  684. requestMatchList(human, true)
  685. end
  686. -- 排行榜前 100
  687. function BZCS_RankList(human)
  688. local msgData = InnerMsg.lw.LW_BZCS_RANK_LIST
  689. msgData.sourceServerId = Config.SVR_INDEX
  690. msgData.playerUuid = human.db._id
  691. sendLwToMiddle(human, msgData)
  692. end
  693. -- 对手头像/战力/积分(按全服名次 rank, 跨服实时解析该名次上的玩家)
  694. function BZCS_OpponentInfo(human, rank)
  695. local opp = resolveMatchOpponentByRank(human, rank)
  696. if not opp then
  697. return Broadcast.sendErr(human, Lang.BZCS_MATCH_OPPONENT_INVALID)
  698. end
  699. local msgData = InnerMsg.lw.LW_BZCS_OPPONENT_INFO
  700. msgData.sourceServerId = Config.SVR_INDEX
  701. msgData.playerUuid = human.db._id
  702. msgData.targetRank = rank
  703. sendLwToMiddle(human, msgData)
  704. end
  705. -- 对手五族阵容(按全服名次 rank)
  706. function BZCS_OpponentLineup(human, rank)
  707. local opp = resolveMatchOpponentByRank(human, rank)
  708. if not opp then
  709. return Broadcast.sendErr(human, Lang.BZCS_MATCH_OPPONENT_INVALID)
  710. end
  711. local msgData = InnerMsg.lw.LW_BZCS_OPPONENT_LINEUP
  712. msgData.sourceServerId = Config.SVR_INDEX
  713. msgData.playerUuid = human.db._id
  714. msgData.targetRank = rank
  715. sendLwToMiddle(human, msgData)
  716. end
  717. -- 己方五族阵容(仅读本地, 不访问跨服)
  718. function BZCS_MyLineup(human)
  719. local msgRet = Msg.gc.GC_BZCS_MY_LINEUP
  720. msgRet.teamList[0] = BaiZhanChengShenDefine.BZCS_RACE_CNT
  721. for i, race in ipairs(BaiZhanChengShenDefine.BZCS_RACE_ORDER) do
  722. local combatType = BaiZhanChengShenDefine.BZCS_RACE_COMBAT_TYPE[race]
  723. local show = getRaceLineupShow(human, race)
  724. msgRet.teamList[i].race = race
  725. fillBzcsTeamNet(msgRet.teamList[i], show, human, combatType)
  726. end
  727. Msg.send(msgRet, human.fd)
  728. end
  729. -- 本地战报列表(存储最老->最新, 协议仍新记录在前)
  730. function BZCS_WarReport(human)
  731. local db = getBzcsDb(human)
  732. local list = db.warReport or {}
  733. local total = #list
  734. local cnt = math.min(total, BaiZhanChengShenDefine.BZCS_WARREPORT_MAX, #Msg.gc.GC_BZCS_WAR_REPORT.reportList)
  735. local msgRet = Msg.gc.GC_BZCS_WAR_REPORT
  736. msgRet.reportList[0] = cnt
  737. for j = 1, cnt do
  738. local r = list[total - j + 1]
  739. local net = msgRet.reportList[j]
  740. net.warType = r.warType or 0
  741. local isRobot = r.isRobot or 0
  742. if isRobot ~= 1 and (r.oppServerId or 0) == 0 then
  743. local warType = r.warType or 0
  744. if warType == BaiZhanChengShenDefine.BZCS_WAR_TYPE_ATK_WIN
  745. or warType == BaiZhanChengShenDefine.BZCS_WAR_TYPE_ATK_LOSE then
  746. isRobot = 1
  747. end
  748. end
  749. net.oppServerId = BaiZhanChengShenDefine.ToClientServerId(r.oppServerId, isRobot)
  750. net.oppName = r.oppName or ""
  751. net.scoreChange = r.scoreChange or 0
  752. end
  753. Msg.send(msgRet, human.fd)
  754. end
  755. -- 排名奖励配置预览(读 excel)
  756. function BZCS_RankReward(human)
  757. local msgRet = Msg.gc.GC_BZCS_RANK_REWARD
  758. msgRet.rewardList[0] = #BzcsConfig.rankReward
  759. for k, v in ipairs(BzcsConfig.rankReward) do
  760. msgRet.rewardList[k].rankLeft = v.rankArea[1]
  761. msgRet.rewardList[k].rankRight = v.rankArea[2]
  762. local items = msgRet.rewardList[k].itemList
  763. items[0] = #v.awardList
  764. for idx, itemCfg in ipairs(v.awardList) do
  765. Grid.makeItem(items[idx], itemCfg[1], itemCfg[2])
  766. end
  767. end
  768. Msg.send(msgRet, human.fd)
  769. end
  770. ------------------------------------ 跨服通知本地服 (InnerHandler.lua -> C2N_*) ------------------------------------
  771. -- 跨服 WL 回调, 组装 GC 或写本地数据
  772. -- WL_BZCS_ACT_START: 记录本轮开始时间(重连补偿广播也会走到这里)
  773. function C2N_Act_Start(msg)
  774. local oldStart = CommonDB.getValueByKey(CommonDB.KEY_BZCS_START_TIME) or 0
  775. local resetCnt = 0
  776. if msg.startTime and msg.startTime > 0 then
  777. CommonDB.updateValue(CommonDB.KEY_BZCS_START_TIME, msg.startTime)
  778. for uuid, human in pairs(ObjHuman.onlineUuid) do
  779. if human.db and human.db.baiZhanChengShen then
  780. local actStart = msg.startTime
  781. if (human.db.baiZhanChengShen.actStartTime or 0) ~= actStart then
  782. human.db.baiZhanChengShen.actStartTime = actStart
  783. clearRoundLocalData(human.db.baiZhanChengShen)
  784. clearMatchCache(human)
  785. resetCnt = resetCnt + 1
  786. end
  787. end
  788. end
  789. end
  790. BzcsLog.logAction("act_open", string.format(
  791. "svr=%s start=%s end=%s oldStart=%s online=%s resetCnt=%s",
  792. Config.SVR_INDEX, msg.startTime or 0, msg.endTime or 0, oldStart,
  793. Util.getTableCount(ObjHuman.onlineUuid), resetCnt
  794. ))
  795. end
  796. -- WL_BZCS_ACT_END: 清除活动开始时间, 并清空在线玩家本轮本地战报/匹配缓存
  797. function C2N_Act_End(msg)
  798. local oldStart = CommonDB.getValueByKey(CommonDB.KEY_BZCS_START_TIME) or 0
  799. CommonDB.updateValue(CommonDB.KEY_BZCS_START_TIME, nil)
  800. local resetCnt = 0
  801. for uuid, human in pairs(ObjHuman.onlineUuid) do
  802. if human.db and human.db.baiZhanChengShen then
  803. clearRoundLocalData(human.db.baiZhanChengShen)
  804. human.db.baiZhanChengShen.actStartTime = 0
  805. clearMatchCache(human)
  806. resetCnt = resetCnt + 1
  807. end
  808. end
  809. BzcsLog.logAction("act_close", string.format(
  810. "svr=%s oldStart=%s online=%s resetCnt=%s",
  811. Config.SVR_INDEX, oldStart, Util.getTableCount(ObjHuman.onlineUuid), resetCnt
  812. ))
  813. end
  814. -- WL_BZCS_TIPS: 跨服业务错误(活动未开/对手无效等)
  815. function C2N_ErrTips(msg)
  816. local human = ObjHuman.onlineUuid[msg.playerUuid]
  817. if not human then return end
  818. local errCode = msg.errCode
  819. if errCode == BaiZhanChengShenDefine.BZCS_ERR_NOT_OPEN then
  820. return Broadcast.sendErr(human, Lang.COMMOM_NOT_ENABLED)
  821. elseif errCode == BaiZhanChengShenDefine.BZCS_ERR_TARGET_INVALID then
  822. return Broadcast.sendErr(human, Lang.BZCS_MATCH_OPPONENT_INVALID)
  823. elseif errCode == BaiZhanChengShenDefine.BZCS_ERR_NO_TIMES then
  824. return Broadcast.sendErr(human, Lang.ITEM_NOT_ENOUGH)
  825. end
  826. Broadcast.sendErr(human, Lang.DATA_ERR)
  827. end
  828. -- WL_BZCS_MATCH -> GC_BZCS_MATCH_LIST, 写入内存匹配缓存
  829. function C2N_Match(msg)
  830. local human = ObjHuman.onlineUuid[msg.playerUuid]
  831. if not human then return end
  832. local myScore = msg.myScore or BaiZhanChengShenDefine.BZCS_INIT_SCORE
  833. local myRank = msg.myRank or 0
  834. local opponentList = msg.opponentList or {}
  835. local cache = getMatchCache(human)
  836. local isDisplayRefresh = cache and cache.matchScore and cache.matchScore == myScore
  837. and cache.opponentList and next(cache.opponentList)
  838. if isDisplayRefresh then
  839. refreshMatchCacheDisplay(human, myScore, opponentList, myRank)
  840. else
  841. setMatchCache(human, myScore, opponentList, myRank)
  842. end
  843. sendMatchListGC(human, myScore, opponentList, myRank)
  844. end
  845. local function fillBzcsRankNet(net, info)
  846. info = info or {}
  847. net.rank = info.rank or 0
  848. net.name = info.name or ""
  849. net.head = info.head or 0
  850. net.headFrame = info.headFrame or 0
  851. net.power = info.power or 0
  852. net.score = info.score or BaiZhanChengShenDefine.BZCS_INIT_SCORE
  853. net.serverId = BaiZhanChengShenDefine.ToClientServerId(info.serverId, info.isRobot or 0)
  854. net.uuid = info.uuid or ""
  855. end
  856. -- 未上榜/未注册跨服时, 用本地五族阵容实时战力(同 CombatPosLogic.getCombatHeroZDL)
  857. local function calcLocalBzcsPower(human)
  858. local total = 0
  859. for _, race in ipairs(BaiZhanChengShenDefine.BZCS_RACE_ORDER) do
  860. local combatType = BaiZhanChengShenDefine.BZCS_RACE_COMBAT_TYPE[race]
  861. total = total + CombatPosLogic.getCombatHeroZDL(human, combatType)
  862. end
  863. return total
  864. end
  865. local function enrichMyRankInfoForClient(human, myInfo)
  866. myInfo = myInfo or {}
  867. if (myInfo.rank or 0) > 0 then
  868. return myInfo
  869. end
  870. return {
  871. rank = 0,
  872. uuid = human.db._id,
  873. name = human.db.name or "",
  874. head = RoleHeadLogic.getRoleAppearance(human, RoleHeadLogic.HEAD_TYPE_1),
  875. headFrame = RoleHeadLogic.getRoleAppearance(human, RoleHeadLogic.HEAD_TYPE_2),
  876. serverId = Config.SVR_INDEX,
  877. power = calcLocalBzcsPower(human),
  878. score = myInfo.score or BaiZhanChengShenDefine.BZCS_INIT_SCORE,
  879. }
  880. end
  881. -- WL_BZCS_RANK_LIST -> GC_BZCS_RANK_LIST
  882. function C2N_RankList(msg)
  883. local human = ObjHuman.onlineUuid[msg.playerUuid]
  884. if not human then return end
  885. local msgRet = Msg.gc.GC_BZCS_RANK_LIST
  886. local myInfo = enrichMyRankInfoForClient(human, msg.myRankInfo)
  887. fillBzcsRankNet(msgRet.myRankInfo, myInfo)
  888. msgRet.rankList[0] = #(msg.rankList or {})
  889. for i, info in ipairs(msg.rankList or {}) do
  890. fillBzcsRankNet(msgRet.rankList[i], info)
  891. end
  892. Msg.send(msgRet, human.fd)
  893. end
  894. -- WL_BZCS_OPPONENT_INFO -> GC_BZCS_OPPONENT_INFO
  895. function C2N_OpponentInfo(msg)
  896. local human = ObjHuman.onlineUuid[msg.playerUuid]
  897. if not human then return end
  898. if msg.res ~= 0 then
  899. return Broadcast.sendErr(human, Lang.DATA_ERR)
  900. end
  901. local t = msg.targetInfo or {}
  902. local msgRet = Msg.gc.GC_BZCS_OPPONENT_INFO
  903. msgRet.name = t.name or ""
  904. msgRet.head = t.head or 0
  905. msgRet.headFrame = t.headFrame or 0
  906. msgRet.power = t.power or 0
  907. msgRet.score = t.score or BaiZhanChengShenDefine.BZCS_INIT_SCORE
  908. Msg.send(msgRet, human.fd)
  909. end
  910. -- WL_BZCS_OPPONENT_LINEUP -> GC_BZCS_OPPONENT_LINEUP
  911. function C2N_OpponentLineup(msg)
  912. local human = ObjHuman.onlineUuid[msg.playerUuid]
  913. if not human then return end
  914. local msgRet = Msg.gc.GC_BZCS_OPPONENT_LINEUP
  915. msgRet.teamList[0] = BaiZhanChengShenDefine.BZCS_RACE_CNT
  916. local heroArr = msg.showInfo and msg.showInfo.heroArr or {}
  917. for i, race in ipairs(BaiZhanChengShenDefine.BZCS_RACE_ORDER) do
  918. local show = heroArr[race] or {}
  919. msgRet.teamList[i].race = race
  920. fillBzcsTeamNet(msgRet.teamList[i], show, nil, nil)
  921. end
  922. Msg.send(msgRet, human.fd)
  923. end
  924. -- WL_BZCS_ISSUE_REWARD: 本服批量发周期结算邮件(rewardList={{uuid,rank},...})
  925. function C2N_IssueReward(msg)
  926. local rewardList = msg.rewardList
  927. if not rewardList or #rewardList == 0 then
  928. return
  929. end
  930. BzcsLog.logAction("reward_issue_ns", string.format("cnt=%s svr=%s", #rewardList, Config.SVR_INDEX))
  931. local q = createRewardQueue()
  932. for _, info in ipairs(rewardList) do
  933. q:add({info[1], info[2]})
  934. end
  935. q:insertDB()
  936. end
  937. ------------------------------------ 战斗相关 (CombatLogic / InnerHandler) ------------------------------------
  938. -- CG_COMBAT_BEGIN / CombatLogic.moduleFn / CombatPosLogic / InnerHandler C2N 战斗回调
  939. -- combatType -> 五族场次下标(1~5)
  940. local function getBzcsRaceIdxByCombatType(combatType)
  941. for raceIdx, t in ipairs(BaiZhanChengShenDefine.BZCS_RACE_COMBAT_TYPE) do
  942. if t == combatType then
  943. return raceIdx
  944. end
  945. end
  946. end
  947. -- CG_COMBAT_BEGIN: 挑战一次依次调用 TYPE39~43; param=全服名次rank(须在内存匹配缓存中)
  948. function fight(human, args, combatType)
  949. local raceIdx = getBzcsRaceIdxByCombatType(combatType)
  950. if not raceIdx then
  951. return Broadcast.sendErr(human, Lang.DATA_ERR)
  952. end
  953. if raceIdx == 1 then
  954. -- 首场: 校验次数/道具、五族阵容, 请求跨服 CAN_FIGHT(扣次在 C2N_CanFight)
  955. ObjHuman.updateDaily(human)
  956. local rank = tonumber(args[1])
  957. if not rank or rank < 1 then
  958. return Broadcast.sendErr(human, Lang.BZCS_MATCH_OPPONENT_INVALID)
  959. end
  960. if human.bzcs_Battle_Cache then
  961. return Broadcast.sendErr(human, Lang.BZCS_FIGHT_STATE_ERR)
  962. end
  963. if not actStartTimeCheck() then
  964. return Broadcast.sendErr(human, Lang.COMMOM_NOT_ENABLED)
  965. end
  966. if not isMiddleReady() then
  967. return Broadcast.sendErr(human, Lang.MIDDLE_SVR_ERR_CONNECT)
  968. end
  969. if not canChallenge(human) then
  970. return Broadcast.sendErr(human, Lang.ITEM_NOT_ENOUGH)
  971. end
  972. if not checkAllRaceLineupForFight(human) then
  973. return
  974. end
  975. if not resolveMatchOpponentByRank(human, rank) then
  976. return Broadcast.sendErr(human, Lang.BZCS_MATCH_OPPONENT_INVALID)
  977. end
  978. local msgData = InnerMsg.lw.LW_BZCS_CAN_FIGHT
  979. msgData.sourceServerId = Config.SVR_INDEX
  980. msgData.playerUuid = human.db._id
  981. msgData.targetRank = rank
  982. sendLwToMiddle(human, msgData)
  983. return
  984. end
  985. -- 第 2~5 场: 已有 bzcs_Battle_Cache 则直接开打, 不再请求跨服/扣次
  986. local cache = human.bzcs_Battle_Cache
  987. if not cache then
  988. return Broadcast.sendErr(human, Lang.BZCS_FIGHT_STATE_ERR)
  989. end
  990. if cache.atkW >= BaiZhanChengShenDefine.BZCS_WIN_TARGET
  991. or cache.defW >= BaiZhanChengShenDefine.BZCS_WIN_TARGET then
  992. return Broadcast.sendErr(human, Lang.BZCS_FIGHT_STATE_ERR)
  993. end
  994. cache.raceIdx = raceIdx
  995. startRaceCombat(human, cache)
  996. end
  997. function getCombatMonsterOutID(human, side, args)
  998. if side ~= CombatDefine.DEFEND_SIDE then return end
  999. local defUuid = args[1]
  1000. local raceIdx = args[2]
  1001. if not defUuid or not raceIdx then return end
  1002. local cfg = BaiZhanChengShenDefine.GetRobotListCfg(defUuid)
  1003. if not cfg or not cfg.monsterOutIDs then return end
  1004. local monsterOutID = cfg.monsterOutIDs[raceIdx]
  1005. if not monsterOutID then return end
  1006. return monsterOutID, BaiZhanChengShenDefine.CalcMonsterOutPower(monsterOutID)
  1007. end
  1008. function getCombatObjList(human, side, args, combatType)
  1009. if side == CombatDefine.ATTACK_SIDE then
  1010. if not human then return end
  1011. return CombatLogic.getHumanObjList(human, combatType)
  1012. end
  1013. -- 防守方: args[2]=raceIdx 为本地机器人, 返回 nil 走 getCombatMonsterOutID
  1014. if args[2] then
  1015. return nil
  1016. end
  1017. local defUuid = args[1]
  1018. if not defUuid then return end
  1019. local db = RoleDBLogic.getDb(defUuid)
  1020. if not db then return end
  1021. return CombatLogic.getHumanObjList({db = db}, combatType)
  1022. end
  1023. function onFightEnd(human, result, combatType, cbParam, combatInfo)
  1024. local cache = human.bzcs_Battle_Cache
  1025. if not cache then return end
  1026. local roundResult = (result == CombatDefine.RESULT_WIN) and 1 or 2
  1027. cache.rounds[cache.raceIdx] = roundResult
  1028. if result == CombatDefine.RESULT_WIN then
  1029. cache.atkW = cache.atkW + 1
  1030. else
  1031. cache.defW = cache.defW + 1
  1032. end
  1033. combatInfo.defender.name = cache.defName
  1034. local seriesOver = cache.atkW >= BaiZhanChengShenDefine.BZCS_WIN_TARGET
  1035. or cache.defW >= BaiZhanChengShenDefine.BZCS_WIN_TARGET
  1036. or cache.raceIdx >= BaiZhanChengShenDefine.BZCS_RACE_CNT
  1037. if not seriesOver then
  1038. cache.raceIdx = cache.raceIdx + 1
  1039. combatInfo.nextCombatType = BaiZhanChengShenDefine.BZCS_RACE_COMBAT_TYPE[cache.raceIdx]
  1040. return
  1041. end
  1042. -- 整场结束: 发挑战奖励并写入 combatInfo.rewardItem(GC_COMBAT_FINISH 展示)
  1043. local atkWin = cache.atkW >= BaiZhanChengShenDefine.BZCS_WIN_TARGET
  1044. grantFightReward(human, atkWin, combatInfo)
  1045. challengeFinish(human, cache)
  1046. end
  1047. function checkUpdatePos(human, msg)
  1048. local condiCamp = COMBATTYPE_2_CAMP[msg.type]
  1049. if not condiCamp then return false end
  1050. HeroLogic = HeroLogic or require("hero.HeroLogic")
  1051. local heroList = Util.split(msg.heroList, ",")
  1052. for i = 1, CombatDefine.COMBAT_HERO_CNT do
  1053. local uuid = heroList[i] or ""
  1054. if uuid ~= "0" and uuid ~= "" then
  1055. local heroGrid = HeroLogic.getHeroGridByUuid(human, uuid)
  1056. if not heroGrid then return false end
  1057. local heroCfg = HeroExcel.hero[heroGrid.id]
  1058. if not heroCfg or heroCfg.camp ~= condiCamp then
  1059. return false
  1060. end
  1061. end
  1062. end
  1063. return true
  1064. end
  1065. function onCombatPosUpdate(human, combatType)
  1066. local race = COMBATTYPE_2_CAMP[combatType]
  1067. if not race then return end
  1068. UpdateShowInfo(human, BaiZhanChengShenDefine.BZCS_UPDATE_SHOW_LINEUP, race)
  1069. end
  1070. -- 百战成神阵容精灵位变更(CombatPosLogic.Elf_Pos_Update 调用)
  1071. function onElfPosUpdate(human, combatType)
  1072. onCombatPosUpdate(human, combatType)
  1073. end
  1074. -- 该族阵容精灵位是否包含指定 elfId
  1075. local function raceLineupHasElf(elfList, elfId)
  1076. if not elfList or not elfId or elfId < 1 then
  1077. return false
  1078. end
  1079. for i = 1, CombatDefine.COMBAT_ELF_NOW_CNT do
  1080. if elfList[i] == elfId then
  1081. return true
  1082. end
  1083. end
  1084. return false
  1085. end
  1086. -- 精灵升星后同步所有上阵了该精灵的百战成神队伍(同精灵可出现在多族阵容)
  1087. function onElfStarUp(human, elfId)
  1088. if not elfId or elfId < 1 then return end
  1089. if not actStartTimeCheck() or not isMiddleReady() then return end
  1090. local db = getBzcsDb(human)
  1091. if not db.crossRegistered then return end
  1092. for _, race in ipairs(BaiZhanChengShenDefine.BZCS_RACE_ORDER) do
  1093. local combatType = BaiZhanChengShenDefine.BZCS_RACE_COMBAT_TYPE[race]
  1094. local combatHeroDB = CombatPosLogic.getCombatHeroDB(human, combatType)
  1095. if raceLineupHasElf(combatHeroDB and combatHeroDB.elfList, elfId) then
  1096. UpdateShowInfo(human, BaiZhanChengShenDefine.BZCS_UPDATE_SHOW_LINEUP, race)
  1097. end
  1098. end
  1099. end
  1100. -- 该族阵容是否包含指定英雄 uuid
  1101. local function raceLineupHasHero(heroList, heroUuid)
  1102. if not heroUuid or heroUuid == "" or heroUuid == "0" then
  1103. return false
  1104. end
  1105. if not heroList then return false end
  1106. for _, uuid in pairs(heroList) do
  1107. if uuid == heroUuid then
  1108. return true
  1109. end
  1110. end
  1111. return false
  1112. end
  1113. -- 收集百战成神五族中上阵了该英雄的种族列表(删除英雄前调用)
  1114. function collectBzcsRacesWithHero(human, heroUuid)
  1115. local races = {}
  1116. if not heroUuid or heroUuid == "" or heroUuid == "0" then
  1117. return races
  1118. end
  1119. for _, race in ipairs(BaiZhanChengShenDefine.BZCS_RACE_ORDER) do
  1120. local combatType = BaiZhanChengShenDefine.BZCS_RACE_COMBAT_TYPE[race]
  1121. local heroList = CombatPosLogic.getCombatHeros(human, combatType)
  1122. if raceLineupHasHero(heroList, heroUuid) then
  1123. races[#races + 1] = race
  1124. end
  1125. end
  1126. return races
  1127. end
  1128. -- 英雄数据变更后同步跨服阵容; raceList 可选(重生等删除英雄后传入删除前收集的种族)
  1129. function onHeroDataUpdate(human, heroUuid, raceList)
  1130. if not actStartTimeCheck() or not isMiddleReady() then return end
  1131. local db = getBzcsDb(human)
  1132. if not db.crossRegistered then return end
  1133. local races = raceList
  1134. if not races or #races < 1 then
  1135. if not heroUuid or heroUuid == "" or heroUuid == "0" then return end
  1136. races = collectBzcsRacesWithHero(human, heroUuid)
  1137. end
  1138. if #races < 1 then return end
  1139. for _, race in ipairs(races) do
  1140. UpdateShowInfo(human, BaiZhanChengShenDefine.BZCS_UPDATE_SHOW_LINEUP, race)
  1141. end
  1142. end
  1143. -- WL_BZCS_CAN_FIGHT: 扣次并初始化 bzcs_Battle_Cache, 开打第一族
  1144. function C2N_CanFight(msg)
  1145. local human = ObjHuman.onlineUuid[msg.playerUuid]
  1146. if not human then return end
  1147. if msg.res ~= 0 then
  1148. return Broadcast.sendErr(human, Lang.DATA_ERR)
  1149. end
  1150. if not deductChallengeTimes(human) then
  1151. return Broadcast.sendErr(human, Lang.ITEM_NOT_ENOUGH)
  1152. end
  1153. human.bzcs_Battle_Cache = {
  1154. defRank = msg.targetRank,
  1155. defUuid = msg.defUuid,
  1156. defServerId = msg.defServerId,
  1157. defName = msg.defName,
  1158. defScore = msg.defScore,
  1159. isRobot = msg.isRobot,
  1160. raceIdx = 1,
  1161. atkW = 0,
  1162. defW = 0,
  1163. rounds = {},
  1164. }
  1165. BzcsLog.logAction("can_fight", string.format(
  1166. "atk=%s defRank=%s def=%s defScore=%s isRobot=%s defSvr=%s",
  1167. msg.playerUuid or "", msg.targetRank or 0, msg.defUuid or "", msg.defScore or 0, msg.isRobot or 0, msg.defServerId or 0
  1168. ))
  1169. startRaceCombat(human, human.bzcs_Battle_Cache)
  1170. end
  1171. -- WL_BZCS_FIGHT_END: 攻方积分变化, 写战报, 重置匹配缓存与 TTL
  1172. function C2N_FightEnd(msg)
  1173. local human = ObjHuman.onlineUuid[msg.playerUuid]
  1174. local offlineSave = false
  1175. if not human then
  1176. local db = RoleDBLogic.getDb(msg.playerUuid, {baiZhanChengShen = 1})
  1177. if not db then return end
  1178. human = {db = db}
  1179. offlineSave = true
  1180. end
  1181. local db = getBzcsDb(human)
  1182. db.crossRegistered = true
  1183. local atkWin = msg.atkWin == 1
  1184. addWarReport(human, {
  1185. warType = atkWin and BaiZhanChengShenDefine.BZCS_WAR_TYPE_ATK_WIN or BaiZhanChengShenDefine.BZCS_WAR_TYPE_ATK_LOSE,
  1186. oppServerId = msg.defServerId or 0,
  1187. oppName = msg.defName or "",
  1188. scoreChange = msg.scoreChange or 0,
  1189. isRobot = msg.defIsRobot or 0,
  1190. })
  1191. if human.fd then
  1192. resetMatchCacheOnScoreChange(human, msg.myScore)
  1193. elseif offlineSave then
  1194. RoleDBLogic.saveRoleSset(human.db)
  1195. end
  1196. BzcsLog.logAction("fight_end_ns", string.format("uuid=%s atkWin=%s scoreChange=%s myScore=%s", msg.playerUuid or "", msg.atkWin or 0, msg.scoreChange or 0, msg.myScore or 0))
  1197. end
  1198. -- WL_BZCS_DEF_NOTIFY: 防守方战报(在线/离线)
  1199. function C2N_DefNotify(msg)
  1200. local human = ObjHuman.onlineUuid[msg.playerUuid]
  1201. local offlineSave = false
  1202. if not human then
  1203. local db = RoleDBLogic.getDb(msg.playerUuid, {baiZhanChengShen = 1})
  1204. if not db then return end
  1205. human = {db = db}
  1206. offlineSave = true
  1207. end
  1208. local defWin = msg.atkWin == 1
  1209. addWarReport(human, {
  1210. warType = defWin and BaiZhanChengShenDefine.BZCS_WAR_TYPE_DEF_WIN or BaiZhanChengShenDefine.BZCS_WAR_TYPE_DEF_LOSE,
  1211. oppServerId = msg.atkServerId or 0,
  1212. oppName = msg.atkName or "",
  1213. scoreChange = msg.scoreChange or 0,
  1214. })
  1215. if human.fd then
  1216. resetMatchCacheOnScoreChange(human, msg.myScore)
  1217. elseif offlineSave then
  1218. RoleDBLogic.saveRoleSset(human.db)
  1219. end
  1220. end
  1221. ------------------------------------ 对外接口 ------------------------------------
  1222. -- 供红点/活动状态/阵容展示同步等其它模块调用
  1223. -- 跨天重置免费挑战次数(由 ObjHuman.updateDaily 调用)
  1224. function updateDaily(human)
  1225. if not human.db.baiZhanChengShen then
  1226. return
  1227. end
  1228. human.db.baiZhanChengShen.freeTimes = BaiZhanChengShenDefine.BZCS_FREE_TIMES
  1229. end
  1230. local ACT_STATE_NOOPEN = 0 -- 活动未开启
  1231. local ACT_STATE_START = 2 -- 活动进行中(与 JjcActLogic.STATE_START 一致)
  1232. -- 本轮活动剩余秒数(至周日23:00)
  1233. local function calcActLeftSec()
  1234. local startTime = CommonDB.getValueByKey(CommonDB.KEY_BZCS_START_TIME)
  1235. if not startTime or startTime == 0 or not IsRunning(startTime) then
  1236. return 0
  1237. end
  1238. local endDayOffset = BaiZhanChengShenDefine.GetOpenEndDayOffset()
  1239. local endTime = Util.getDayStartTime(startTime) + endDayOffset * 86400
  1240. + BaiZhanChengShenDefine.BZCS_END_SEC
  1241. local now = os.time()
  1242. if endTime > now then
  1243. return endTime - now
  1244. end
  1245. return 0
  1246. end
  1247. -- 百战成神活动是否处于开启状态(挑战日 + 跨服已开轮且在活动期内)
  1248. -- function ModuleisOpen(human)
  1249. -- return actStartTimeCheck()
  1250. -- end
  1251. -- 玩法状态与剩余时间: state 0=未开启 2=进行中; leftSec=剩余秒数
  1252. function getActState(human)
  1253. if not actStartTimeCheck() then
  1254. return ACT_STATE_NOOPEN, 0
  1255. end
  1256. return ACT_STATE_START, calcActLeftSec()
  1257. end
  1258. -- 活动红点: 可参与且仍有免费挑战次数
  1259. function isActRed(human)
  1260. if not isServerEligible() then return false end
  1261. if not actStartTimeCheck() then return false end
  1262. local db = getBzcsDb(human)
  1263. return db.freeTimes > 0
  1264. end
  1265. -- joinTime 为跨服下发的本轮开始时间; 无参时仅判断星期
  1266. function IsRunning(joinTime)
  1267. if not joinTime then
  1268. return true
  1269. end
  1270. local wDay = getWDay()
  1271. if wDay > BaiZhanChengShenDefine.BZCS_OPEN_WDAY_AREA[2]
  1272. and wDay < BaiZhanChengShenDefine.BZCS_OPEN_WDAY_AREA[1] then
  1273. return false
  1274. end
  1275. local now = os.time()
  1276. if now < joinTime then
  1277. return false
  1278. end
  1279. local endDayOffset = BaiZhanChengShenDefine.GetOpenEndDayOffset()
  1280. local endTime = Util.getDayStartTime(joinTime) + endDayOffset * 86400
  1281. + BaiZhanChengShenDefine.BZCS_END_SEC
  1282. if now > endTime then
  1283. return false
  1284. end
  1285. local dayStart = Util.getDayStartTime(now)
  1286. if wDay == BaiZhanChengShenDefine.BZCS_OPEN_WDAY_AREA[2]
  1287. and now > (dayStart + BaiZhanChengShenDefine.BZCS_END_SEC) then
  1288. return false
  1289. end
  1290. local diffDays = Util.diffDay(joinTime)
  1291. if diffDays > BaiZhanChengShenDefine.BZCS_OPEN_DAYS then
  1292. return false
  1293. end
  1294. return true
  1295. end
  1296. -- 增量同步跨服展示(LW_BZCS_UPDATE_SHOW, 须已注册)
  1297. -- updateType: BZCS_UPDATE_SHOW_NAME/HEAD/HEAD_FRAME/BODY/LINEUP; LINEUP 时 race 必填(1~5)
  1298. function UpdateShowInfo(human, updateType, race)
  1299. if not actStartTimeCheck() or not isMiddleReady() then return end
  1300. if not updateType then return end
  1301. local db = getBzcsDb(human)
  1302. if not db.crossRegistered then
  1303. return
  1304. end
  1305. local patch = buildShowPatch(human, updateType, race)
  1306. if not patch then return end
  1307. local msgData = InnerMsg.lw.LW_BZCS_UPDATE_SHOW
  1308. msgData.playerUuid = human.db._id
  1309. msgData.updateType = updateType
  1310. msgData.race = race or 0
  1311. msgData.showInfo = patch
  1312. InnerMsg.sendMsg(0, msgData)
  1313. BzcsLog.logAction("update_show_send", string.format("uuid=%s type=%s race=%s", human.db._id, updateType, race or 0))
  1314. end