BaiZhanChengShenNS.lua 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400
  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)
  140. human.bzcs_Match_Cache = {
  141. myScore = myScore,
  142. matchScore = myScore,
  143. opponentList = opponentList or {},
  144. cacheTime = os.time(),
  145. }
  146. end
  147. -- 展示刷新: 保留 matchScore(匹配基准), 更新对手展示并重置 TTL
  148. local function refreshMatchCacheDisplay(human, myScore, opponentList)
  149. local cache = getMatchCache(human)
  150. human.bzcs_Match_Cache = {
  151. myScore = myScore,
  152. matchScore = cache and cache.matchScore or myScore,
  153. opponentList = opponentList or {},
  154. cacheTime = os.time(),
  155. }
  156. end
  157. -- 积分变动: 清空对手列表, 重置 matchScore/TTL(从变动时刻重新计时)
  158. local function resetMatchCacheOnScoreChange(human, newScore)
  159. if not human then return end
  160. newScore = newScore or BaiZhanChengShenDefine.BZCS_INIT_SCORE
  161. human.bzcs_Match_Cache = {
  162. myScore = newScore,
  163. matchScore = newScore,
  164. opponentList = {},
  165. cacheTime = os.time(),
  166. }
  167. end
  168. local function isMatchCacheFresh(human)
  169. local cache = getMatchCache(human)
  170. if not cache or not cache.opponentList or not next(cache.opponentList) then
  171. return false
  172. end
  173. if (cache.myScore or 0) ~= (cache.matchScore or 0) then
  174. return false
  175. end
  176. local ttl = BaiZhanChengShenDefine.BZCS_MATCH_CACHE_TTL or 30
  177. return (os.time() - (cache.cacheTime or 0)) < ttl
  178. end
  179. local function collectRefreshRanks(opponentList)
  180. local ranks = {}
  181. for _, opp in ipairs(opponentList or {}) do
  182. if opp.rank and opp.rank > 0 then
  183. ranks[#ranks + 1] = opp.rank
  184. end
  185. end
  186. return ranks
  187. end
  188. -- 校验 rank 是否属于当前匹配缓存(全服名次, 非列表下标 1~3)
  189. local function resolveMatchOpponentByRank(human, rank)
  190. if not rank or rank < 1 then
  191. return nil
  192. end
  193. local cache = getMatchCache(human)
  194. for _, opp in ipairs(cache and cache.opponentList or {}) do
  195. if opp.rank == rank and opp.uuid then
  196. return opp
  197. end
  198. end
  199. return nil
  200. end
  201. -- 指定种族阵容是否至少上阵 1 名英雄(空位 "0"/"" 不算)
  202. local function hasRaceLineupHero(human, race)
  203. local combatType = BaiZhanChengShenDefine.BZCS_RACE_COMBAT_TYPE[race]
  204. if not combatType then return false end
  205. local heroList = CombatPosLogic.getCombatHeros(human, combatType)
  206. if not heroList then return false end
  207. for _, heroUuid in pairs(heroList) do
  208. if heroUuid and heroUuid ~= "" and heroUuid ~= "0" then
  209. return true
  210. end
  211. end
  212. return false
  213. end
  214. -- 五族阵容是否均已至少上阵 1 名英雄(供 GC_BZCS_MATCH_LIST.allLineupReady)
  215. local function isAllRaceLineupReady(human)
  216. for _, race in ipairs(BaiZhanChengShenDefine.BZCS_RACE_ORDER) do
  217. if not hasRaceLineupHero(human, race) then
  218. return false
  219. end
  220. end
  221. return true
  222. end
  223. -- 挑战前校验五族阵容均已上阵英雄
  224. local function checkAllRaceLineupForFight(human)
  225. for _, race in ipairs(BaiZhanChengShenDefine.BZCS_RACE_ORDER) do
  226. if not hasRaceLineupHero(human, race) then
  227. local raceName = BaiZhanChengShenDefine.BZCS_RACE_NAME[race] or ""
  228. Broadcast.sendErr(human, Util.format(Lang.BZCS_NEED_RACE_LINEUP, raceName))
  229. return false
  230. end
  231. end
  232. return true
  233. end
  234. -- 组装并下发 GC_BZCS_MATCH_LIST
  235. local function sendMatchListGC(human, myScore, opponentList)
  236. local db = getBzcsDb(human)
  237. local msgRet = Msg.gc.GC_BZCS_MATCH_LIST
  238. msgRet.myScore = myScore or BaiZhanChengShenDefine.BZCS_INIT_SCORE
  239. msgRet.freeTimes = db.freeTimes or 0
  240. Grid.makeItem(
  241. msgRet.ticketCost,
  242. BaiZhanChengShenDefine.BZCS_TICKET_ITEM_ID,
  243. BaiZhanChengShenDefine.BZCS_TICKET_COST
  244. )
  245. msgRet.allLineupReady = isAllRaceLineupReady(human) and 1 or 0
  246. msgRet.opponentList[0] = #(opponentList or {})
  247. for i, opp in ipairs(opponentList or {}) do
  248. local net = msgRet.opponentList[i]
  249. net.rank = opp.rank or 0
  250. net.name = opp.name
  251. net.body = opp.body or 0
  252. net.power = opp.power
  253. net.score = opp.score
  254. net.serverId = BaiZhanChengShenDefine.ToClientServerId(opp.serverId, opp.isRobot)
  255. end
  256. Msg.send(msgRet, human.fd)
  257. end
  258. -- 单个英雄展示字段(阵容/协议用)
  259. local function getHeroInfo(human, heroUuid)
  260. HeroLogic = HeroLogic or require("hero.HeroLogic")
  261. local heroGrid = HeroLogic.getHeroGridByUuid(human, heroUuid)
  262. if not heroGrid then return end
  263. local heroCfg = HeroExcel.hero[heroGrid.id]
  264. RoleAttr.calcHeroGrid(heroGrid, nil, human)
  265. return {
  266. heroUuid = heroUuid,
  267. heroStar = heroGrid.star,
  268. heroLevel = heroGrid.lv,
  269. heroCamp = heroGrid.camp or heroCfg.camp,
  270. heroBody = heroCfg.body,
  271. heroIcon = heroGrid.head or heroCfg.head,
  272. heroPower = heroGrid.zhandouli,
  273. heroId = heroGrid.id,
  274. heroQuality = heroCfg.grade,
  275. }
  276. end
  277. -- 某战斗类型阵容内已上阵英雄展示列表
  278. local function getRaceHeroListInfo(human, combatType)
  279. local heroArr = {}
  280. local len = 0
  281. local heroList = CombatPosLogic.getCombatHeros(human, combatType)
  282. if not heroList then
  283. return heroArr
  284. end
  285. for _, heroUuid in pairs(heroList) do
  286. if heroUuid ~= "" and heroUuid ~= "0" then
  287. local heroInfo = getHeroInfo(human, heroUuid)
  288. if heroInfo then
  289. len = len + 1
  290. heroArr[len] = heroInfo
  291. end
  292. end
  293. end
  294. return heroArr
  295. end
  296. -- 技能 icon(助阵凤凰 moshouSkill 用)
  297. local function getBzcsSkillIcon(skillId)
  298. if not skillId or skillId < 1 then
  299. return ""
  300. end
  301. local skillConfig = SkillExcel.skill[skillId] or Skill.GetSkillConfig(skillId)
  302. return skillConfig and skillConfig.icon or ""
  303. end
  304. -- 填充精灵位展示(同 CombatPosLogic.Elf_Pos_Query)
  305. local function fillBzcsElfPosArr(msgElfPosArr, elfList, elfSkillIds, human)
  306. ElfLogic = ElfLogic or require("elf.ElfLogic")
  307. msgElfPosArr[0] = CombatDefine.COMBAT_ELF_NOW_CNT
  308. for i = 1, CombatDefine.COMBAT_ELF_NOW_CNT do
  309. local net = msgElfPosArr[i]
  310. net.elfId = 0
  311. net.nowSkillDesc = ""
  312. net.nowSkillIcon = ""
  313. local elfId = elfList and elfList[i]
  314. if elfId and elfId > 0 then
  315. local skillId
  316. if human then
  317. skillId = ElfLogic.GetElfSkill(human, elfId)
  318. else
  319. skillId = elfSkillIds and elfSkillIds[i]
  320. end
  321. if skillId and skillId > 0 then
  322. local skillConfig = Skill.GetSkillConfig(skillId)
  323. if skillConfig then
  324. net.elfId = elfId
  325. net.nowSkillDesc = skillConfig.desc
  326. net.nowSkillIcon = skillConfig.icon
  327. end
  328. end
  329. end
  330. end
  331. end
  332. -- 填充 BZCS_TEAM_INFO(英雄/助阵 icon/精灵展示)
  333. local function fillBzcsTeamNet(teamNet, show, human, combatType)
  334. show = BaiZhanChengShenDefine.ExpandBzcsRaceShow(show) or show
  335. local heroList = show.heroArr or {}
  336. teamNet.power = BaiZhanChengShenDefine.CalcRaceTeamPower(show)
  337. teamNet.formation = show.formation or 1
  338. teamNet.heroArr[0] = #heroList
  339. for j, h in ipairs(heroList) do
  340. local net = teamNet.heroArr[j]
  341. net.heroBody = h.heroBody or 0
  342. net.heroStar = h.heroStar or 0
  343. net.heroLv = h.heroLevel or h.heroLv or 0
  344. net.heroCamp = h.heroCamp or 0
  345. net.heroIcon = h.heroIcon or 0
  346. net.heroId = h.heroId or 0
  347. net.heroQuality = h.heroQuality or 1
  348. end
  349. local helpSkillId = show.helpSkillId or 0
  350. if human and combatType and helpSkillId < 1 then
  351. MoshouLogic = MoshouLogic or require("moshou.MoshouLogic")
  352. local combatHeroDB = CombatPosLogic.getCombatHeroDB(human, combatType)
  353. if combatHeroDB and combatHeroDB.helpList then
  354. helpSkillId = MoshouLogic.getPutMoshouSkillID(human, combatType, combatHeroDB.helpList) or 0
  355. end
  356. end
  357. teamNet.moshouSkill = getBzcsSkillIcon(helpSkillId)
  358. fillBzcsElfPosArr(teamNet.elfPosArr, show.elfList, show.elfSkillIds, human)
  359. end
  360. -- 单种族阵容展示(跨服仅存 heroArr/阵法/助阵技能id/精灵id+技能id; 战力见各英雄 heroPower)
  361. local function getRaceLineupShow(human, race)
  362. local combatType = BaiZhanChengShenDefine.BZCS_RACE_COMBAT_TYPE[race]
  363. local heroArr = getRaceHeroListInfo(human, combatType)
  364. local combatHeroDB = CombatPosLogic.getCombatHeroDB(human, combatType)
  365. local helpSkillId = 0
  366. local elfList, elfSkillIds
  367. if combatHeroDB then
  368. MoshouLogic = MoshouLogic or require("moshou.MoshouLogic")
  369. ElfLogic = ElfLogic or require("elf.ElfLogic")
  370. if combatHeroDB.helpList then
  371. helpSkillId = MoshouLogic.getPutMoshouSkillID(human, combatType, combatHeroDB.helpList) or 0
  372. end
  373. if combatHeroDB.elfList then
  374. elfList = {}
  375. elfSkillIds = {}
  376. for i = 1, CombatDefine.COMBAT_ELF_NOW_CNT do
  377. local elfId = combatHeroDB.elfList[i]
  378. if elfId and elfId > 0 then
  379. elfList[i] = elfId
  380. elfSkillIds[i] = ElfLogic.GetElfSkill(human, elfId) or 0
  381. end
  382. end
  383. end
  384. end
  385. return {
  386. heroArr = heroArr,
  387. formation = combatHeroDB and combatHeroDB.formation or 1,
  388. helpSkillId = helpSkillId,
  389. elfList = elfList,
  390. elfSkillIds = elfSkillIds,
  391. }
  392. end
  393. -- 跨服展示包: 外观 + 五族阵容(存入 DB.playerList[].showInfo, 仅 REGISTER 全量使用)
  394. local function buildShowInfo(human)
  395. local heroArr = {}
  396. for _, race in ipairs(BaiZhanChengShenDefine.BZCS_RACE_ORDER) do
  397. heroArr[race] = getRaceLineupShow(human, race)
  398. end
  399. return {
  400. name = human.db.name,
  401. head = RoleHeadLogic.getRoleAppearance(human, RoleHeadLogic.HEAD_TYPE_1),
  402. headFrame = RoleHeadLogic.getRoleAppearance(human, RoleHeadLogic.HEAD_TYPE_2),
  403. body = RoleHeadLogic.getRoleAppearance(human, RoleHeadLogic.HEAD_TYPE_3),
  404. heroArr = heroArr,
  405. }
  406. end
  407. -- 按 updateType 组装增量 showInfo(仅含本次变更字段)
  408. local function buildShowPatch(human, updateType, race)
  409. if updateType == BaiZhanChengShenDefine.BZCS_UPDATE_SHOW_NAME then
  410. return {name = human.db.name}
  411. elseif updateType == BaiZhanChengShenDefine.BZCS_UPDATE_SHOW_HEAD then
  412. return {head = RoleHeadLogic.getRoleAppearance(human, RoleHeadLogic.HEAD_TYPE_1)}
  413. elseif updateType == BaiZhanChengShenDefine.BZCS_UPDATE_SHOW_HEAD_FRAME then
  414. return {headFrame = RoleHeadLogic.getRoleAppearance(human, RoleHeadLogic.HEAD_TYPE_2)}
  415. elseif updateType == BaiZhanChengShenDefine.BZCS_UPDATE_SHOW_BODY then
  416. return {body = RoleHeadLogic.getRoleAppearance(human, RoleHeadLogic.HEAD_TYPE_3)}
  417. elseif updateType == BaiZhanChengShenDefine.BZCS_UPDATE_SHOW_LINEUP then
  418. if not race then return end
  419. return {heroArr = {[race] = getRaceLineupShow(human, race)}}
  420. end
  421. end
  422. -- 按排名取周期奖励道具配置
  423. local function getRankReward(rank)
  424. for _, v in ipairs(BzcsConfig.rankReward) do
  425. if rank >= v.rankArea[1] and rank <= v.rankArea[2] then
  426. return v.awardList
  427. end
  428. end
  429. end
  430. -- 道具列表转日志串 itemId:cnt,...
  431. local function formatAwardList(awardList)
  432. if not awardList or #awardList < 1 then
  433. return ""
  434. end
  435. local parts = {}
  436. for i, item in ipairs(awardList) do
  437. parts[i] = string.format("%s:%s", item[1] or 0, item[2] or 0)
  438. end
  439. return table.concat(parts, ",")
  440. end
  441. -- 整场挑战结束发放道具奖励(读 excel fightReward: 1=胜 2=负)
  442. -- combatInfo.rewardItem 供 GC_COMBAT_FINISH 展示, 参考 BattleLogic.onFightEnd
  443. local function grantFightReward(human, atkWin, combatInfo)
  444. if not human or not human.db then return end
  445. local rewardId = atkWin and BaiZhanChengShenDefine.BZCS_FIGHT_REWARD_WIN_ID
  446. or BaiZhanChengShenDefine.BZCS_FIGHT_REWARD_LOSE_ID
  447. local fightReward = BzcsConfig.fightReward
  448. local cfg = fightReward and fightReward[rewardId]
  449. if not cfg or not cfg.awardList or not next(cfg.awardList) then
  450. BzcsLog.logAction("fight_reward_miss", string.format(
  451. "uuid=%s atkWin=%s rewardId=%s", human.db._id, atkWin and 1 or 0, rewardId
  452. ))
  453. return
  454. end
  455. if combatInfo then
  456. combatInfo.rewardItem = {}
  457. end
  458. local awardList = {}
  459. for i, itemInfo in ipairs(cfg.awardList) do
  460. local itemID = itemInfo[1]
  461. local itemCnt = itemInfo[2]
  462. awardList[i] = {itemID, itemCnt}
  463. if combatInfo then
  464. combatInfo.rewardItem[i] = {itemID, itemCnt}
  465. end
  466. BagLogic.addItem(human, itemID, itemCnt, LOGTAG)
  467. end
  468. BzcsLog.logAction("fight_reward", string.format(
  469. "uuid=%s atkWin=%s rewardId=%s itemCnt=%s items=%s",
  470. human.db._id, atkWin and 1 or 0, rewardId, #awardList, formatAwardList(awardList)
  471. ))
  472. end
  473. -- 发周期结算邮件(排名写入正文)
  474. local function sendMail(mailId, receiverUuid, itemArray, rank)
  475. local mailCfg = MailExcel.mail[mailId]
  476. if not mailCfg then
  477. BzcsLog.logAction("reward_mail_miss", string.format(
  478. "uuid=%s rank=%s mailId=%s", receiverUuid or "", rank or 0, mailId or 0
  479. ))
  480. return
  481. end
  482. if not itemArray or not next(itemArray) then
  483. BzcsLog.logAction("reward_mail_empty", string.format(
  484. "uuid=%s rank=%s mailId=%s", receiverUuid or "", rank or 0, mailId
  485. ))
  486. return
  487. end
  488. local content = mailCfg.content
  489. if rank then
  490. if rank > BaiZhanChengShenDefine.BZCS_RANK_MAX then
  491. rank = "100+"
  492. end
  493. content = Util.format(content, rank)
  494. end
  495. MailManager.add(MailManager.SYSTEM, receiverUuid, mailCfg.title, content, itemArray, mailCfg.senderName or "GM")
  496. BzcsLog.logAction("reward_mail_ok", string.format(
  497. "uuid=%s rank=%s mailId=%s itemCnt=%s items=%s",
  498. receiverUuid, rank or 0, mailId, #itemArray, formatAwardList(itemArray)
  499. ))
  500. end
  501. -- 批量发奖队列(分批发邮件, 失败重试)
  502. local function createRewardQueue()
  503. local q = {playerArray = {}, insertMaxNum = 100, repeatMaxTimes = 3, repeatTb = {}}
  504. function q:add(info) table.insert(self.playerArray, info) end
  505. function q:insertDB()
  506. local maxNum = math.min(self.insertMaxNum, #self.playerArray)
  507. for _ = 1, maxNum do
  508. local info = table.remove(self.playerArray)
  509. local uuid, rank = info[1], info[2]
  510. local itemArray = getRankReward(rank)
  511. if not itemArray then
  512. BzcsLog.logAction("reward_cfg_miss", string.format("uuid=%s rank=%s", uuid or "", rank or 0))
  513. else
  514. local ok, err = pcall(sendMail, BaiZhanChengShenDefine.BZCS_AWARD_MAIL_ID, uuid, itemArray, rank)
  515. if not ok then
  516. local retry = (self.repeatTb[uuid] or 0) + 1
  517. if retry <= self.repeatMaxTimes then
  518. self.repeatTb[uuid] = retry
  519. q:add(info)
  520. BzcsLog.logAction("reward_mail_fail", string.format(
  521. "uuid=%s rank=%s retry=%s err=%s", uuid, rank, retry, err
  522. ))
  523. else
  524. BzcsLog.logAction("reward_mail_giveup", string.format(
  525. "uuid=%s rank=%s retry=%s err=%s", uuid, rank, retry, err
  526. ))
  527. end
  528. end
  529. end
  530. end
  531. if #self.playerArray > 0 then
  532. Timer.addLater(3, self.insertDB, self)
  533. end
  534. end
  535. return q
  536. end
  537. -- bzcs_Battle_Cache: defRank/defUuid/defServerId/defName/defScore/isRobot, raceIdx, atkW/defW, rounds
  538. -- 战报尾插; 超上限删最老; 下发 GC 时倒序
  539. local function addWarReport(human, report)
  540. local db = getBzcsDb(human)
  541. db.warReport = db.warReport or {}
  542. db.warReport[#db.warReport + 1] = report
  543. if #db.warReport > BaiZhanChengShenDefine.BZCS_WARREPORT_MAX then
  544. table.remove(db.warReport, 1)
  545. end
  546. end
  547. -- 是否仍有挑战次数(免费或道具115); 仅校验不扣
  548. local function canChallenge(human)
  549. local db = getBzcsDb(human)
  550. if db.freeTimes > 0 then
  551. return true
  552. end
  553. return BagLogic.getItemCnt(human, BaiZhanChengShenDefine.BZCS_TICKET_ITEM_ID) >= BaiZhanChengShenDefine.BZCS_TICKET_COST
  554. end
  555. -- 优先扣免费次数, 否则扣门票; 整场仅在 C2N_CanFight 扣 1 次
  556. local function deductChallengeTimes(human)
  557. local db = getBzcsDb(human)
  558. if db.freeTimes > 0 then
  559. db.freeTimes = db.freeTimes - 1
  560. return true
  561. end
  562. if BagLogic.getItemCnt(human, BaiZhanChengShenDefine.BZCS_TICKET_ITEM_ID) < BaiZhanChengShenDefine.BZCS_TICKET_COST then
  563. return false
  564. end
  565. BagLogic.delItem(human, BaiZhanChengShenDefine.BZCS_TICKET_ITEM_ID, BaiZhanChengShenDefine.BZCS_TICKET_COST, LOGTAG)
  566. return true
  567. end
  568. -- 本轮首次完成挑战时向跨服注册, 每轮仅一次
  569. local function tryRegisterToCross(human)
  570. local db = getBzcsDb(human)
  571. if db.crossRegistered then
  572. return
  573. end
  574. local reg = InnerMsg.lw.LW_BZCS_REGISTER
  575. reg.playerInfo = {
  576. uuid = human.db._id,
  577. serverId = Config.SVR_INDEX,
  578. firstJoinTime = os.time(),
  579. showInfo = buildShowInfo(human),
  580. }
  581. InnerMsg.sendMsg(0, reg)
  582. BzcsLog.logAction("register_send", string.format("uuid=%s serverId=%s", human.db._id, Config.SVR_INDEX))
  583. end
  584. -- 按 raceIdx 开打当前族; 机器人本地战, 真人跨服
  585. local function startRaceCombat(human, cache)
  586. local raceIdx = cache.raceIdx
  587. local combatType = BaiZhanChengShenDefine.BZCS_RACE_COMBAT_TYPE[raceIdx]
  588. if cache.isRobot == 1 then
  589. CombatLogic.combatBegin(human, nil, {cache.defUuid, raceIdx}, combatType)
  590. else
  591. MiddleCommonLogic.MiddleCommonLogic_CombatBegin_LW(human, {
  592. combatType = combatType,
  593. nServerIndex = cache.defServerId,
  594. param = cache.defUuid,
  595. })
  596. end
  597. end
  598. -- 5 局系列结束: 注册跨服(首次) -> LW_BZCS_FIGHT_END, 攻方战报, 清 cache
  599. local function challengeFinish(human, cache)
  600. if not isMiddleReady() then
  601. BzcsLog.logAction("fight_end_middle_down", string.format("uuid=%s def=%s", human.db._id, cache.defUuid or ""))
  602. if human.fd then
  603. Broadcast.sendErr(human, Lang.MIDDLE_SVR_ERR_CONNECT)
  604. end
  605. human.bzcs_Battle_Cache = nil
  606. return
  607. end
  608. -- 须先于 FIGHT_END: 跨服 UpdateScore 依赖 playerList 中已有攻方
  609. tryRegisterToCross(human)
  610. local atkWin = cache.atkW >= BaiZhanChengShenDefine.BZCS_WIN_TARGET
  611. local msgData = InnerMsg.lw.LW_BZCS_FIGHT_END
  612. msgData.atkUuid = human.db._id
  613. msgData.atkServerId = Config.SVR_INDEX
  614. msgData.atkName = human.db.name
  615. msgData.defUuid = cache.defUuid
  616. msgData.defName = cache.defName
  617. msgData.defServerId = cache.defServerId
  618. msgData.atkWin = atkWin and 1 or 0
  619. msgData.raceResults = cache.rounds
  620. InnerMsg.sendMsg(0, msgData)
  621. BzcsLog.logAction("fight_end_send", string.format(
  622. "atk=%s def=%s atkWin=%s isRobot=%s atkW=%s defW=%s",
  623. human.db._id, cache.defUuid or "", msgData.atkWin, cache.isRobot or 0, cache.atkW, cache.defW
  624. ))
  625. local scoreChange = atkWin and BaiZhanChengShenDefine.BZCS_ATK_WIN_SCORE or BaiZhanChengShenDefine.BZCS_ATK_LOSE_SCORE
  626. addWarReport(human, {
  627. warType = atkWin and BaiZhanChengShenDefine.BZCS_WAR_TYPE_ATK_WIN or BaiZhanChengShenDefine.BZCS_WAR_TYPE_ATK_LOSE,
  628. oppServerId = cache.defServerId or 0,
  629. oppName = cache.defName or "",
  630. scoreChange = scoreChange,
  631. isRobot = cache.isRobot or 0,
  632. })
  633. human.bzcs_Battle_Cache = nil
  634. end
  635. ------------------------------------ 客户端请求 (Handler.lua -> BZCS_*) ------------------------------------
  636. -- 由 Handler.lua 转发; 需跨服的走 sendLwToMiddle, 仅本地数据的直接 GC
  637. -- 请求跨服匹配列表; forceRefresh=true 时全量重匹配
  638. local function requestMatchList(human, forceRefresh)
  639. if human.fd then
  640. ObjHuman.updateDaily(human)
  641. end
  642. getBzcsDb(human)
  643. if not isServerEligible() then
  644. return Broadcast.sendErr(human, Lang.COMMOM_NOT_ENABLED)
  645. end
  646. if not actStartTimeCheck() then
  647. return Broadcast.sendErr(human, Lang.COMMOM_NOT_ENABLED)
  648. end
  649. if forceRefresh then
  650. clearMatchCache(human)
  651. end
  652. if isMatchCacheFresh(human) then
  653. local cache = getMatchCache(human)
  654. return sendMatchListGC(human, cache.myScore, cache.opponentList)
  655. end
  656. if not isMiddleReady() then
  657. return Broadcast.sendErr(human, Lang.MIDDLE_SVR_ERR_CONNECT)
  658. end
  659. local cache = getMatchCache(human)
  660. local msgData = InnerMsg.lw.LW_BZCS_MATCH
  661. msgData.sourceServerId = Config.SVR_INDEX
  662. msgData.playerUuid = human.db._id
  663. msgData.refreshRanks = {}
  664. if cache and cache.opponentList and next(cache.opponentList)
  665. and (cache.myScore or 0) == (cache.matchScore or 0) then
  666. msgData.refreshRanks = collectRefreshRanks(cache.opponentList)
  667. end
  668. if not sendLwToMiddle(human, msgData) then
  669. return
  670. end
  671. end
  672. -- 匹配主界面: 下发 myScore + 3 名对手(score/name/power)
  673. -- 内存缓存未过期直返; 过期则跨服按 rank 刷新展示; 无缓存则全量匹配
  674. function BZCS_MatchList(human)
  675. requestMatchList(human, false)
  676. end
  677. -- 客户端刷新匹配对手(清缓存后跨服按积分±500步进重匹配3人, 回包 GC_BZCS_MATCH_LIST)
  678. function BZCS_MatchRefresh(human)
  679. if not actStartTimeCheck() then
  680. return Broadcast.sendErr(human, Lang.COMMOM_NOT_ENABLED)
  681. end
  682. if human.bzcs_Battle_Cache then
  683. return Broadcast.sendErr(human, Lang.DATA_ERR)
  684. end
  685. requestMatchList(human, true)
  686. end
  687. -- 排行榜前 100
  688. function BZCS_RankList(human)
  689. local msgData = InnerMsg.lw.LW_BZCS_RANK_LIST
  690. msgData.sourceServerId = Config.SVR_INDEX
  691. msgData.playerUuid = human.db._id
  692. sendLwToMiddle(human, msgData)
  693. end
  694. -- 对手头像/战力/积分(按全服名次 rank, 跨服实时解析该名次上的玩家)
  695. function BZCS_OpponentInfo(human, rank)
  696. local opp = resolveMatchOpponentByRank(human, rank)
  697. if not opp then
  698. return Broadcast.sendErr(human, Lang.BZCS_MATCH_OPPONENT_INVALID)
  699. end
  700. local msgData = InnerMsg.lw.LW_BZCS_OPPONENT_INFO
  701. msgData.sourceServerId = Config.SVR_INDEX
  702. msgData.playerUuid = human.db._id
  703. msgData.targetRank = rank
  704. sendLwToMiddle(human, msgData)
  705. end
  706. -- 对手五族阵容(按全服名次 rank)
  707. function BZCS_OpponentLineup(human, rank)
  708. local opp = resolveMatchOpponentByRank(human, rank)
  709. if not opp then
  710. return Broadcast.sendErr(human, Lang.BZCS_MATCH_OPPONENT_INVALID)
  711. end
  712. local msgData = InnerMsg.lw.LW_BZCS_OPPONENT_LINEUP
  713. msgData.sourceServerId = Config.SVR_INDEX
  714. msgData.playerUuid = human.db._id
  715. msgData.targetRank = rank
  716. sendLwToMiddle(human, msgData)
  717. end
  718. -- 己方五族阵容(仅读本地, 不访问跨服)
  719. function BZCS_MyLineup(human)
  720. local msgRet = Msg.gc.GC_BZCS_MY_LINEUP
  721. msgRet.teamList[0] = BaiZhanChengShenDefine.BZCS_RACE_CNT
  722. for i, race in ipairs(BaiZhanChengShenDefine.BZCS_RACE_ORDER) do
  723. local combatType = BaiZhanChengShenDefine.BZCS_RACE_COMBAT_TYPE[race]
  724. local show = getRaceLineupShow(human, race)
  725. msgRet.teamList[i].race = race
  726. fillBzcsTeamNet(msgRet.teamList[i], show, human, combatType)
  727. end
  728. Msg.send(msgRet, human.fd)
  729. end
  730. -- 本地战报列表(存储最老->最新, 协议仍新记录在前)
  731. function BZCS_WarReport(human)
  732. local db = getBzcsDb(human)
  733. local list = db.warReport or {}
  734. local total = #list
  735. local cnt = math.min(total, BaiZhanChengShenDefine.BZCS_WARREPORT_MAX, #Msg.gc.GC_BZCS_WAR_REPORT.reportList)
  736. local msgRet = Msg.gc.GC_BZCS_WAR_REPORT
  737. msgRet.reportList[0] = cnt
  738. for j = 1, cnt do
  739. local r = list[total - j + 1]
  740. local net = msgRet.reportList[j]
  741. net.warType = r.warType or 0
  742. local isRobot = r.isRobot or 0
  743. if isRobot ~= 1 and (r.oppServerId or 0) == 0 then
  744. local warType = r.warType or 0
  745. if warType == BaiZhanChengShenDefine.BZCS_WAR_TYPE_ATK_WIN
  746. or warType == BaiZhanChengShenDefine.BZCS_WAR_TYPE_ATK_LOSE then
  747. isRobot = 1
  748. end
  749. end
  750. net.oppServerId = BaiZhanChengShenDefine.ToClientServerId(r.oppServerId, isRobot)
  751. net.oppName = r.oppName or ""
  752. net.scoreChange = r.scoreChange or 0
  753. end
  754. Msg.send(msgRet, human.fd)
  755. end
  756. -- 排名奖励配置预览(读 excel)
  757. function BZCS_RankReward(human)
  758. local msgRet = Msg.gc.GC_BZCS_RANK_REWARD
  759. msgRet.rewardList[0] = #BzcsConfig.rankReward
  760. for k, v in ipairs(BzcsConfig.rankReward) do
  761. msgRet.rewardList[k].rankLeft = v.rankArea[1]
  762. msgRet.rewardList[k].rankRight = v.rankArea[2]
  763. local items = msgRet.rewardList[k].itemList
  764. items[0] = #v.awardList
  765. for idx, itemCfg in ipairs(v.awardList) do
  766. Grid.makeItem(items[idx], itemCfg[1], itemCfg[2])
  767. end
  768. end
  769. Msg.send(msgRet, human.fd)
  770. end
  771. ------------------------------------ 跨服通知本地服 (InnerHandler.lua -> C2N_*) ------------------------------------
  772. -- 跨服 WL 回调, 组装 GC 或写本地数据
  773. -- WL_BZCS_ACT_START: 记录本轮开始时间(重连补偿广播也会走到这里)
  774. function C2N_Act_Start(msg)
  775. local oldStart = CommonDB.getValueByKey(CommonDB.KEY_BZCS_START_TIME) or 0
  776. local resetCnt = 0
  777. if msg.startTime and msg.startTime > 0 then
  778. CommonDB.updateValue(CommonDB.KEY_BZCS_START_TIME, msg.startTime)
  779. for uuid, human in pairs(ObjHuman.onlineUuid) do
  780. if human.db and human.db.baiZhanChengShen then
  781. local actStart = msg.startTime
  782. if (human.db.baiZhanChengShen.actStartTime or 0) ~= actStart then
  783. human.db.baiZhanChengShen.actStartTime = actStart
  784. clearRoundLocalData(human.db.baiZhanChengShen)
  785. clearMatchCache(human)
  786. resetCnt = resetCnt + 1
  787. end
  788. end
  789. end
  790. end
  791. BzcsLog.logAction("act_open", string.format(
  792. "svr=%s start=%s end=%s oldStart=%s online=%s resetCnt=%s",
  793. Config.SVR_INDEX, msg.startTime or 0, msg.endTime or 0, oldStart,
  794. Util.getTableCount(ObjHuman.onlineUuid), resetCnt
  795. ))
  796. end
  797. -- WL_BZCS_ACT_END: 清除活动开始时间, 并清空在线玩家本轮本地战报/匹配缓存
  798. function C2N_Act_End(msg)
  799. local oldStart = CommonDB.getValueByKey(CommonDB.KEY_BZCS_START_TIME) or 0
  800. CommonDB.updateValue(CommonDB.KEY_BZCS_START_TIME, nil)
  801. local resetCnt = 0
  802. for uuid, human in pairs(ObjHuman.onlineUuid) do
  803. if human.db and human.db.baiZhanChengShen then
  804. clearRoundLocalData(human.db.baiZhanChengShen)
  805. human.db.baiZhanChengShen.actStartTime = 0
  806. clearMatchCache(human)
  807. resetCnt = resetCnt + 1
  808. end
  809. end
  810. BzcsLog.logAction("act_close", string.format(
  811. "svr=%s oldStart=%s online=%s resetCnt=%s",
  812. Config.SVR_INDEX, oldStart, Util.getTableCount(ObjHuman.onlineUuid), resetCnt
  813. ))
  814. end
  815. -- WL_BZCS_TIPS: 跨服业务错误(活动未开/对手无效等)
  816. function C2N_ErrTips(msg)
  817. local human = ObjHuman.onlineUuid[msg.playerUuid]
  818. if not human then return end
  819. local errCode = msg.errCode
  820. if errCode == BaiZhanChengShenDefine.BZCS_ERR_NOT_OPEN then
  821. return Broadcast.sendErr(human, Lang.COMMOM_NOT_ENABLED)
  822. elseif errCode == BaiZhanChengShenDefine.BZCS_ERR_TARGET_INVALID then
  823. return Broadcast.sendErr(human, Lang.BZCS_MATCH_OPPONENT_INVALID)
  824. elseif errCode == BaiZhanChengShenDefine.BZCS_ERR_NO_TIMES then
  825. return Broadcast.sendErr(human, Lang.ITEM_NOT_ENOUGH)
  826. end
  827. Broadcast.sendErr(human, Lang.DATA_ERR)
  828. end
  829. -- WL_BZCS_MATCH -> GC_BZCS_MATCH_LIST, 写入内存匹配缓存
  830. function C2N_Match(msg)
  831. local human = ObjHuman.onlineUuid[msg.playerUuid]
  832. if not human then return end
  833. local myScore = msg.myScore or BaiZhanChengShenDefine.BZCS_INIT_SCORE
  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)
  840. else
  841. setMatchCache(human, myScore, opponentList)
  842. end
  843. sendMatchListGC(human, myScore, opponentList)
  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. if not human then return end
  1175. local db = getBzcsDb(human)
  1176. db.crossRegistered = true
  1177. resetMatchCacheOnScoreChange(human, msg.myScore)
  1178. 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))
  1179. end
  1180. -- WL_BZCS_DEF_NOTIFY: 防守方战报(在线/离线)
  1181. function C2N_DefNotify(msg)
  1182. local human = ObjHuman.onlineUuid[msg.playerUuid]
  1183. local offlineSave = false
  1184. if not human then
  1185. local db = RoleDBLogic.getDb(msg.playerUuid, {baiZhanChengShen = 1})
  1186. if not db then return end
  1187. human = {db = db}
  1188. offlineSave = true
  1189. end
  1190. local defWin = msg.atkWin == 1
  1191. addWarReport(human, {
  1192. warType = defWin and BaiZhanChengShenDefine.BZCS_WAR_TYPE_DEF_WIN or BaiZhanChengShenDefine.BZCS_WAR_TYPE_DEF_LOSE,
  1193. oppServerId = msg.atkServerId or 0,
  1194. oppName = msg.atkName or "",
  1195. scoreChange = msg.scoreChange or 0,
  1196. })
  1197. if human.fd then
  1198. resetMatchCacheOnScoreChange(human, msg.myScore)
  1199. elseif offlineSave then
  1200. RoleDBLogic.saveRoleSset(human.db)
  1201. end
  1202. end
  1203. ------------------------------------ 对外接口 ------------------------------------
  1204. -- 供红点/活动状态/阵容展示同步等其它模块调用
  1205. -- 跨天重置免费挑战次数(由 ObjHuman.updateDaily 调用)
  1206. function updateDaily(human)
  1207. if not human.db.baiZhanChengShen then
  1208. return
  1209. end
  1210. human.db.baiZhanChengShen.freeTimes = BaiZhanChengShenDefine.BZCS_FREE_TIMES
  1211. end
  1212. local ACT_STATE_NOOPEN = 0 -- 活动未开启
  1213. local ACT_STATE_START = 2 -- 活动进行中(与 JjcActLogic.STATE_START 一致)
  1214. -- 本轮活动剩余秒数(至周日23:00)
  1215. local function calcActLeftSec()
  1216. local startTime = CommonDB.getValueByKey(CommonDB.KEY_BZCS_START_TIME)
  1217. if not startTime or startTime == 0 or not IsRunning(startTime) then
  1218. return 0
  1219. end
  1220. local endDayOffset = BaiZhanChengShenDefine.GetOpenEndDayOffset()
  1221. local endTime = Util.getDayStartTime(startTime) + endDayOffset * 86400
  1222. + BaiZhanChengShenDefine.BZCS_END_SEC
  1223. local now = os.time()
  1224. if endTime > now then
  1225. return endTime - now
  1226. end
  1227. return 0
  1228. end
  1229. -- 百战成神活动是否处于开启状态(挑战日 + 跨服已开轮且在活动期内)
  1230. function ModuleisOpen(human)
  1231. return actStartTimeCheck()
  1232. end
  1233. -- 玩法状态与剩余时间: state 0=未开启 2=进行中; leftSec=剩余秒数
  1234. function getActState(human)
  1235. if not actStartTimeCheck() then
  1236. return ACT_STATE_NOOPEN, 0
  1237. end
  1238. return ACT_STATE_START, calcActLeftSec()
  1239. end
  1240. -- 活动红点: 可参与且仍有免费挑战次数
  1241. function isActRed(human)
  1242. if not isServerEligible() then return false end
  1243. if not actStartTimeCheck() then return false end
  1244. local db = getBzcsDb(human)
  1245. return db.freeTimes > 0
  1246. end
  1247. -- joinTime 为跨服下发的本轮开始时间; 无参时仅判断星期
  1248. function IsRunning(joinTime)
  1249. if not joinTime then
  1250. return true
  1251. end
  1252. local wDay = getWDay()
  1253. if wDay > BaiZhanChengShenDefine.BZCS_OPEN_WDAY_AREA[2]
  1254. and wDay < BaiZhanChengShenDefine.BZCS_OPEN_WDAY_AREA[1] then
  1255. return false
  1256. end
  1257. local now = os.time()
  1258. if now < joinTime then
  1259. return false
  1260. end
  1261. local endDayOffset = BaiZhanChengShenDefine.GetOpenEndDayOffset()
  1262. local endTime = Util.getDayStartTime(joinTime) + endDayOffset * 86400
  1263. + BaiZhanChengShenDefine.BZCS_END_SEC
  1264. if now > endTime then
  1265. return false
  1266. end
  1267. local dayStart = Util.getDayStartTime(now)
  1268. if wDay == BaiZhanChengShenDefine.BZCS_OPEN_WDAY_AREA[2]
  1269. and now > (dayStart + BaiZhanChengShenDefine.BZCS_END_SEC) then
  1270. return false
  1271. end
  1272. local diffDays = Util.diffDay(joinTime)
  1273. if diffDays > BaiZhanChengShenDefine.BZCS_OPEN_DAYS then
  1274. return false
  1275. end
  1276. return true
  1277. end
  1278. -- 增量同步跨服展示(LW_BZCS_UPDATE_SHOW, 须已注册)
  1279. -- updateType: BZCS_UPDATE_SHOW_NAME/HEAD/HEAD_FRAME/BODY/LINEUP; LINEUP 时 race 必填(1~5)
  1280. function UpdateShowInfo(human, updateType, race)
  1281. if not actStartTimeCheck() or not isMiddleReady() then return end
  1282. if not updateType then return end
  1283. local db = getBzcsDb(human)
  1284. if not db.crossRegistered then
  1285. return
  1286. end
  1287. local patch = buildShowPatch(human, updateType, race)
  1288. if not patch then return end
  1289. local msgData = InnerMsg.lw.LW_BZCS_UPDATE_SHOW
  1290. msgData.playerUuid = human.db._id
  1291. msgData.updateType = updateType
  1292. msgData.race = race or 0
  1293. msgData.showInfo = patch
  1294. InnerMsg.sendMsg(0, msgData)
  1295. BzcsLog.logAction("update_show_send", string.format("uuid=%s type=%s race=%s", human.db._id, updateType, race or 0))
  1296. end