BaiZhanChengShenNS.lua 52 KB

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