import type { DailyShopParsed } from '../DailyShopTypes'; import { BattleBuffFunctionType, passiveBuffUsesBattleGameCd } from './BattleBuffTypes'; import { parseBattleBuffAspdWan, parseBattleBuffBaseHpBonus, parseBattleBuffBaseThunderConfig, parseBattleBuffBossSpawnBonusWan, parseBattleBuffBothAttackSpeedConfig, parseBattleBuffBothBaseHpConfig, parseBattleBuffGoldMineIntervalSec, parseBattleBuffKillCoinProbWan, parseBattleBuffMultiplierCardConfig, parseBattleBuffUnlockCoinConfig, parseBattleBuffWan, parseFunctionParNums, type BattleBuffBaseThunderConfig, type BattleBuffBothAttackSpeedConfig, type BattleBuffBothBaseHpConfig, type BattleBuffMultiplierCardConfig, type BattleBuffUnlockCoinConfig, } from './BattleBuffParamParse'; export { passiveBuffUsesBattleGameCd }; /** 万分比满值 */ const WAN_DENOM = 10000; /** 去重累加携带被动数值 */ function accumulateUniqueBuffNums(passives: readonly DailyShopParsed[], matchFt: BattleBuffFunctionType, readNum: (row: ITableBattlebuff) => number): { active: boolean; total: number } { let total = 0; let active = false; const seenIds: number[] = []; for (let i = 0; i < passives.length; i++) { const row = passives[i].row as ITableBattlebuff | undefined; if (!row || Number(row.functiontype) !== matchFt) continue; active = true; const buffId = Number(row.id) || 0; if (buffId <= 0) continue; let duplicated = false; for (let j = 0; j < seenIds.length; j++) { if (seenIds[j] === buffId) { duplicated = true; break; } } if (duplicated) continue; seenIds.push(buffId); total += readNum(row); } return { active, total }; } /** 读取被动 buff 局内 CD 秒数(金矿:functionpar 首段) */ export function readBattleBuffPassiveCdSec(row: ITableBattlebuff | null | undefined): number { if (!row) return 0; const ft = Number(row.functiontype) || 0; if (!passiveBuffUsesBattleGameCd(ft)) return 0; if (ft === BattleBuffFunctionType.GoldMine) { return parseBattleBuffGoldMineIntervalSec(row.functionpar); } const nums = parseFunctionParNums(row.functionpar); if (nums.length < 1) { console.log('[BattleBuff] 被动 buff functionpar 缺少 CD', row.id, row.functionpar); return 0; } return Math.max(0, Math.floor(nums[0])); } /** 灾厄盒:Boss 出现加成万分比汇总 */ export function resolveCarriedBossSpawnBonusWan(passives?: readonly DailyShopParsed[]): { active: boolean; bonusWan: number } { const list = passives ?? []; const r = accumulateUniqueBuffNums(list, BattleBuffFunctionType.BossSpawnRate, (row) => parseBattleBuffBossSpawnBonusWan(row.functionpar)); return { active: r.active, bonusWan: r.total }; } /** 攻速符:己方场上攻速万分比 */ export function resolveCarriedUnitAttackSpeedWan(passives?: readonly DailyShopParsed[]): { active: boolean; bonusWan: number } { const list = passives ?? []; const r = accumulateUniqueBuffNums(list, BattleBuffFunctionType.UnitAttackSpeed, (row) => parseBattleBuffAspdWan(row.functionpar)); return { active: r.active, bonusWan: r.total }; } /** 鏖战册汇总 */ export type BattleBuffBothAttackSpeedAdjust = { /** 是否携带 */ active: boolean; /** 我方攻速万分比 */ selfWan: number; /** 对方攻速万分比 */ enemyWan: number; }; /** 汇总鏖战册 */ export function resolveCarriedBothAttackSpeed(passives?: readonly DailyShopParsed[]): BattleBuffBothAttackSpeedAdjust { const list = passives ?? []; let selfWan = 0; let enemyWan = 0; let active = false; const seenIds: number[] = []; for (let i = 0; i < list.length; i++) { const row = list[i].row as ITableBattlebuff | undefined; if (!row || Number(row.functiontype) !== BattleBuffFunctionType.BothAttackSpeed) continue; active = true; const buffId = Number(row.id) || 0; if (buffId <= 0) continue; let duplicated = false; for (let j = 0; j < seenIds.length; j++) { if (seenIds[j] === buffId) { duplicated = true; break; } } if (duplicated) continue; seenIds.push(buffId); const cfg: BattleBuffBothAttackSpeedConfig | null = parseBattleBuffBothAttackSpeedConfig(row.functionpar); if (!cfg) continue; selfWan += cfg.selfWan; enemyWan += cfg.enemyWan; } return { active, selfWan, enemyWan }; } /** 赏金令:击杀额外金币掷骰,成功返回 1 否则 0 */ export function rollCarriedKillCoinBonus(passives?: readonly DailyShopParsed[]): number { const list = passives ?? []; let probWan = 0; const seenIds: number[] = []; for (let i = 0; i < list.length; i++) { const row = list[i].row as ITableBattlebuff | undefined; if (!row || Number(row.functiontype) !== BattleBuffFunctionType.KillCoin) continue; const buffId = Number(row.id) || 0; if (buffId <= 0) continue; let duplicated = false; for (let j = 0; j < seenIds.length; j++) { if (seenIds[j] === buffId) { duplicated = true; break; } } if (duplicated) continue; seenIds.push(buffId); probWan += parseBattleBuffKillCoinProbWan(row.functionpar); } if (probWan <= 0) return 0; const clamped = Math.min(WAN_DENOM, probWan); return Math.random() * WAN_DENOM < clamped ? 1 : 0; } /** 倍增卡:召唤数字 ×2 万分比 */ export function resolveCarriedDigitDoubleWan(passives?: readonly DailyShopParsed[]): { active: boolean; doubleWan: number } { const list = passives ?? []; const r = accumulateUniqueBuffNums(list, BattleBuffFunctionType.DigitDouble, (row) => parseBattleBuffWan(row.functionpar, '倍增卡')); return { active: r.active, doubleWan: r.total }; } /** 幸运石:宝石掉落加成万分比 */ export function resolveCarriedGemDropBonusWan(passives?: readonly DailyShopParsed[]): { active: boolean; bonusWan: number } { const list = passives ?? []; const r = accumulateUniqueBuffNums(list, BattleBuffFunctionType.GemDrop, (row) => parseBattleBuffWan(row.functionpar, '幸运石')); return { active: r.active, bonusWan: r.total }; } /** 气血丸:基地加血 */ export function resolveCarriedBaseHpBonus(passives?: readonly DailyShopParsed[]): { active: boolean; bonusHp: number } { const list = passives ?? []; const r = accumulateUniqueBuffNums(list, BattleBuffFunctionType.BaseHp, (row) => parseBattleBuffBaseHpBonus(row.functionpar)); return { active: r.active, bonusHp: r.total }; } /** 双生盾汇总 */ export type BattleBuffBothBaseHpAdjust = { /** 是否携带 */ active: boolean; /** 我方加血 */ selfHp: number; /** 对方加血 */ enemyHp: number; }; /** 汇总双生盾 */ export function resolveCarriedBothBaseHp(passives?: readonly DailyShopParsed[]): BattleBuffBothBaseHpAdjust { const list = passives ?? []; let selfHp = 0; let enemyHp = 0; let active = false; const seenIds: number[] = []; for (let i = 0; i < list.length; i++) { const row = list[i].row as ITableBattlebuff | undefined; if (!row || Number(row.functiontype) !== BattleBuffFunctionType.BothBaseHp) continue; active = true; const buffId = Number(row.id) || 0; if (buffId <= 0) continue; let duplicated = false; for (let j = 0; j < seenIds.length; j++) { if (seenIds[j] === buffId) { duplicated = true; break; } } if (duplicated) continue; seenIds.push(buffId); const cfg: BattleBuffBothBaseHpConfig | null = parseBattleBuffBothBaseHpConfig(row.functionpar); if (!cfg) continue; selfHp += cfg.selfHp; enemyHp += cfg.enemyHp; } return { active, selfHp, enemyHp }; } /** 寻金尺配置汇总(取首个有效;多条时 min/max 分别取最宽) */ export function resolveCarriedUnlockCoin(passives?: readonly DailyShopParsed[]): BattleBuffUnlockCoinConfig | null { const list = passives ?? []; let minCoin = 0; let maxCoin = 0; let any = false; const seenIds: number[] = []; for (let i = 0; i < list.length; i++) { const row = list[i].row as ITableBattlebuff | undefined; if (!row || Number(row.functiontype) !== BattleBuffFunctionType.UnlockCoin) continue; const buffId = Number(row.id) || 0; if (buffId <= 0) continue; let duplicated = false; for (let j = 0; j < seenIds.length; j++) { if (seenIds[j] === buffId) { duplicated = true; break; } } if (duplicated) continue; seenIds.push(buffId); const cfg = parseBattleBuffUnlockCoinConfig(row.functionpar); if (!cfg) continue; if (!any) { minCoin = cfg.minCoin; maxCoin = cfg.maxCoin; any = true; } else { minCoin = Math.min(minCoin, cfg.minCoin); maxCoin = Math.max(maxCoin, cfg.maxCoin); } } return any ? { minCoin, maxCoin } : null; } /** 御雷珠汇总 */ export type BattleBuffBaseThunderAdjust = { /** 是否携带 */ active: boolean; /** 单局最多触发次数 */ maxTriggers: number; /** 小怪伤害比例万分比 */ smallDmgWan: number; /** Boss 伤害比例万分比 */ bossDmgWan: number; /** 触发距离格数 */ triggerCells: number; /** 生效距离格数 */ effectCells: number; }; /** 汇总御雷珠 */ export function resolveCarriedBaseThunderConfig(passives?: readonly DailyShopParsed[]): BattleBuffBaseThunderAdjust { const list = passives ?? []; const empty: BattleBuffBaseThunderAdjust = { active: false, maxTriggers: 0, smallDmgWan: 0, bossDmgWan: 0, triggerCells: 0, effectCells: 0, }; const seenIds: number[] = []; for (let i = 0; i < list.length; i++) { const row = list[i].row as ITableBattlebuff | undefined; if (!row || Number(row.functiontype) !== BattleBuffFunctionType.BaseThunder) continue; const buffId = Number(row.id) || 0; if (buffId <= 0) continue; let duplicated = false; for (let j = 0; j < seenIds.length; j++) { if (seenIds[j] === buffId) { duplicated = true; break; } } if (duplicated) continue; seenIds.push(buffId); const cfg: BattleBuffBaseThunderConfig | null = parseBattleBuffBaseThunderConfig(row.functionpar); if (!cfg) continue; empty.active = true; empty.maxTriggers += cfg.maxTriggers; empty.smallDmgWan = Math.max(empty.smallDmgWan, cfg.smallDmgWan); empty.bossDmgWan = Math.max(empty.bossDmgWan, cfg.bossDmgWan); empty.triggerCells = Math.max(empty.triggerCells, cfg.triggerCells); empty.effectCells = Math.max(empty.effectCells, cfg.effectCells); } return empty; } /** 省金契:召唤消耗降低万分比 */ export function resolveCarriedSummonCostReduceWan(passives?: readonly DailyShopParsed[]): { active: boolean; reduceWan: number } { const list = passives ?? []; const r = accumulateUniqueBuffNums(list, BattleBuffFunctionType.SummonCost, (row) => parseBattleBuffWan(row.functionpar, '省金契')); return { active: r.active, reduceWan: r.total }; } /** 滞行牌:己方出征减速万分比 */ export function resolveCarriedOwnMarchSlowWan(passives?: readonly DailyShopParsed[]): { active: boolean; slowWan: number } { const list = passives ?? []; const r = accumulateUniqueBuffNums(list, BattleBuffFunctionType.OwnMarchSlow, (row) => parseBattleBuffWan(row.functionpar, '滞行牌')); return { active: r.active, slowWan: r.total }; } /** 倍率卡配置(首个有效行;多条时产卡万分比累加、权重取最后一条) */ export function resolveCarriedMultiplierCard(passives?: readonly DailyShopParsed[]): BattleBuffMultiplierCardConfig | null { const list = passives ?? []; let spawnWan = 0; let plus1Weight = 0; let plus2Weight = 0; let any = false; const seenIds: number[] = []; for (let i = 0; i < list.length; i++) { const row = list[i].row as ITableBattlebuff | undefined; if (!row || Number(row.functiontype) !== BattleBuffFunctionType.MultiplierCard) continue; const buffId = Number(row.id) || 0; if (buffId <= 0) continue; let duplicated = false; for (let j = 0; j < seenIds.length; j++) { if (seenIds[j] === buffId) { duplicated = true; break; } } if (duplicated) continue; seenIds.push(buffId); const cfg = parseBattleBuffMultiplierCardConfig(row.functionpar); if (!cfg) continue; any = true; spawnWan += cfg.spawnWan; plus1Weight = cfg.plus1Weight; plus2Weight = cfg.plus2Weight; } if (!any || spawnWan <= 0) return null; return { spawnWan, plus1Weight, plus2Weight }; } /** 属性卡产卡万分比 */ export function resolveCarriedAttrCardSpawnWan(passives?: readonly DailyShopParsed[]): { active: boolean; spawnWan: number } { const list = passives ?? []; const r = accumulateUniqueBuffNums(list, BattleBuffFunctionType.AttrCard, (row) => parseBattleBuffWan(row.functionpar, '属性卡')); return { active: r.active, spawnWan: r.total }; } /** 是否被动金矿 */ export function isBattleBuffPassiveGoldMine(row: ITableBattlebuff | null | undefined): boolean { if (!row) return false; return Number(row.functiontype) === BattleBuffFunctionType.GoldMine; } /** 是否御雷珠(近基地降雷) */ export function isBattleBuffPassiveBaseThunder(row: ITableBattlebuff | null | undefined): boolean { if (!row) return false; return Number(row.functiontype) === BattleBuffFunctionType.BaseThunder; }