|
|
|
|
import { DailyShopDataManager } from '../DailyShopDataManager';
|
|
|
|
|
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 ?? DailyShopDataManager.getOwnedPassive();
|
|
|
|
|
return accumulateUniqueBuffNums(list, BattleBuffFunctionType.BossSpawnRate, (row) => parseBattleBuffBossSpawnBonusWan(row.functionpar));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 攻速符:己方场上攻速万分比 */
|
|
|
|
|
export function resolveCarriedUnitAttackSpeedWan(passives?: readonly DailyShopParsed[]): { active: boolean; bonusWan: number } {
|
|
|
|
|
const list = passives ?? DailyShopDataManager.getOwnedPassive();
|
|
|
|
|
return accumulateUniqueBuffNums(list, BattleBuffFunctionType.UnitAttackSpeed, (row) => parseBattleBuffAspdWan(row.functionpar));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 鏖战册汇总 */
|
|
|
|
|
export type BattleBuffBothAttackSpeedAdjust = {
|
|
|
|
|
/** 是否携带 */
|
|
|
|
|
active: boolean;
|
|
|
|
|
/** 我方攻速万分比 */
|
|
|
|
|
selfWan: number;
|
|
|
|
|
/** 对方攻速万分比 */
|
|
|
|
|
enemyWan: number;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 汇总鏖战册 */
|
|
|
|
|
export function resolveCarriedBothAttackSpeed(passives?: readonly DailyShopParsed[]): BattleBuffBothAttackSpeedAdjust {
|
|
|
|
|
const list = passives ?? DailyShopDataManager.getOwnedPassive();
|
|
|
|
|
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 ?? DailyShopDataManager.getOwnedPassive();
|
|
|
|
|
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 ?? DailyShopDataManager.getOwnedPassive();
|
|
|
|
|
return accumulateUniqueBuffNums(list, BattleBuffFunctionType.DigitDouble, (row) => parseBattleBuffWan(row.functionpar, '倍增卡'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 幸运石:宝石掉落加成万分比 */
|
|
|
|
|
export function resolveCarriedGemDropBonusWan(passives?: readonly DailyShopParsed[]): { active: boolean; bonusWan: number } {
|
|
|
|
|
const list = passives ?? DailyShopDataManager.getOwnedPassive();
|
|
|
|
|
return accumulateUniqueBuffNums(list, BattleBuffFunctionType.GemDrop, (row) => parseBattleBuffWan(row.functionpar, '幸运石'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 气血丸:基地加血 */
|
|
|
|
|
export function resolveCarriedBaseHpBonus(passives?: readonly DailyShopParsed[]): { active: boolean; bonusHp: number } {
|
|
|
|
|
const list = passives ?? DailyShopDataManager.getOwnedPassive();
|
|
|
|
|
return accumulateUniqueBuffNums(list, BattleBuffFunctionType.BaseHp, (row) => parseBattleBuffBaseHpBonus(row.functionpar));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 双生盾汇总 */
|
|
|
|
|
export type BattleBuffBothBaseHpAdjust = {
|
|
|
|
|
/** 是否携带 */
|
|
|
|
|
active: boolean;
|
|
|
|
|
/** 我方加血 */
|
|
|
|
|
selfHp: number;
|
|
|
|
|
/** 对方加血 */
|
|
|
|
|
enemyHp: number;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 汇总双生盾 */
|
|
|
|
|
export function resolveCarriedBothBaseHp(passives?: readonly DailyShopParsed[]): BattleBuffBothBaseHpAdjust {
|
|
|
|
|
const list = passives ?? DailyShopDataManager.getOwnedPassive();
|
|
|
|
|
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 ?? DailyShopDataManager.getOwnedPassive();
|
|
|
|
|
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 ?? DailyShopDataManager.getOwnedPassive();
|
|
|
|
|
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 ?? DailyShopDataManager.getOwnedPassive();
|
|
|
|
|
return accumulateUniqueBuffNums(list, BattleBuffFunctionType.SummonCost, (row) => parseBattleBuffWan(row.functionpar, '省金契'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 滞行牌:己方出征减速万分比 */
|
|
|
|
|
export function resolveCarriedOwnMarchSlowWan(passives?: readonly DailyShopParsed[]): { active: boolean; slowWan: number } {
|
|
|
|
|
const list = passives ?? DailyShopDataManager.getOwnedPassive();
|
|
|
|
|
return accumulateUniqueBuffNums(list, BattleBuffFunctionType.OwnMarchSlow, (row) => parseBattleBuffWan(row.functionpar, '滞行牌'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 倍率卡配置(首个有效行;多条时产卡万分比累加、权重取最后一条) */
|
|
|
|
|
export function resolveCarriedMultiplierCard(passives?: readonly DailyShopParsed[]): BattleBuffMultiplierCardConfig | null {
|
|
|
|
|
const list = passives ?? DailyShopDataManager.getOwnedPassive();
|
|
|
|
|
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 ?? DailyShopDataManager.getOwnedPassive();
|
|
|
|
|
return accumulateUniqueBuffNums(list, BattleBuffFunctionType.AttrCard, (row) => parseBattleBuffWan(row.functionpar, '属性卡'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 是否被动金矿 */
|
|
|
|
|
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;
|
|
|
|
|
}
|