You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
AdLike2048/assets/script/manager/battleBuff/BattleBuffPassiveSkillUtil.ts

363 lines
14 KiB

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;
}