|
|
|
|
/**
|
|
|
|
|
* 解析 Battlebuff.functionpar 中带符号整数。
|
|
|
|
|
* 支持 `+10`、`-5`、`10`、数值 10。
|
|
|
|
|
* @returns 整数;无法解析时 null
|
|
|
|
|
*/
|
|
|
|
|
export function parseSignedIntFromFunctionPar(raw: string | number | null | undefined): number | null {
|
|
|
|
|
if (raw == null || raw === '') return null;
|
|
|
|
|
if (typeof raw === 'number') {
|
|
|
|
|
return Number.isFinite(raw) ? Math.trunc(raw) : null;
|
|
|
|
|
}
|
|
|
|
|
const text = String(raw).trim();
|
|
|
|
|
if (!text) return null;
|
|
|
|
|
const m = /^([+-]?)(\d+(?:\.\d+)?)$/.exec(text);
|
|
|
|
|
if (!m) return null;
|
|
|
|
|
const sign = m[1] === '-' ? -1 : 1;
|
|
|
|
|
const n = Number(m[2]);
|
|
|
|
|
if (!Number.isFinite(n)) return null;
|
|
|
|
|
return sign * Math.trunc(n);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 解析 functionpar 为逗号分隔数字列表。
|
|
|
|
|
* @param raw 表字段
|
|
|
|
|
*/
|
|
|
|
|
export function parseFunctionParNums(raw: string | number | null | undefined): number[] {
|
|
|
|
|
if (raw == null || raw === '') return [];
|
|
|
|
|
if (typeof raw === 'number') {
|
|
|
|
|
return Number.isFinite(raw) ? [Math.trunc(raw)] : [];
|
|
|
|
|
}
|
|
|
|
|
const text = String(raw).trim();
|
|
|
|
|
if (!text) return [];
|
|
|
|
|
const parts = text.split(',');
|
|
|
|
|
const out: number[] = [];
|
|
|
|
|
for (let i = 0; i < parts.length; i++) {
|
|
|
|
|
const n = parseSignedIntFromFunctionPar(parts[i].trim());
|
|
|
|
|
if (n != null) out.push(n);
|
|
|
|
|
}
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 万分比满值 */
|
|
|
|
|
const WAN_DENOM = 10000;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 解析体力 buff 增量(functiontype=15)。
|
|
|
|
|
* @returns 正整数增量;无效时 0
|
|
|
|
|
*/
|
|
|
|
|
export function parseBattleBuffVigourDelta(raw: string | number | null | undefined): number {
|
|
|
|
|
const n = parseSignedIntFromFunctionPar(raw);
|
|
|
|
|
if (n == null || n <= 0) return 0;
|
|
|
|
|
return n;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 灾厄盒:Boss 出现几率加成万分比 */
|
|
|
|
|
export function parseBattleBuffBossSpawnBonusWan(raw: string | number | null | undefined): number {
|
|
|
|
|
const nums = parseFunctionParNums(raw);
|
|
|
|
|
if (nums.length < 1) {
|
|
|
|
|
console.log('[BattleBuff] 灾厄盒 functionpar 缺少万分比');
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
return Math.max(0, Math.floor(nums[0]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 倍率卡配置 */
|
|
|
|
|
export type BattleBuffMultiplierCardConfig = {
|
|
|
|
|
/** 召唤时产卡概率万分比 */
|
|
|
|
|
spawnWan: number;
|
|
|
|
|
/** +1 级权重 */
|
|
|
|
|
plus1Weight: number;
|
|
|
|
|
/** +2 级权重 */
|
|
|
|
|
plus2Weight: number;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 解析倍率卡 functionpar:产卡万分比,+1权重,+2权重 */
|
|
|
|
|
export function parseBattleBuffMultiplierCardConfig(raw: string | number | null | undefined): BattleBuffMultiplierCardConfig | null {
|
|
|
|
|
const nums = parseFunctionParNums(raw);
|
|
|
|
|
if (nums.length < 3) {
|
|
|
|
|
console.log('[BattleBuff] 倍率卡 functionpar 需 3 段');
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
const spawnWan = Math.max(0, Math.floor(nums[0]));
|
|
|
|
|
const plus1Weight = Math.max(0, Math.floor(nums[1]));
|
|
|
|
|
const plus2Weight = Math.max(0, Math.floor(nums[2]));
|
|
|
|
|
if (spawnWan <= 0 || plus1Weight + plus2Weight <= 0) return null;
|
|
|
|
|
return { spawnWan, plus1Weight, plus2Weight };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 攻速万分比(单值) */
|
|
|
|
|
export function parseBattleBuffAspdWan(raw: string | number | null | undefined): number {
|
|
|
|
|
const nums = parseFunctionParNums(raw);
|
|
|
|
|
if (nums.length < 1) {
|
|
|
|
|
console.log('[BattleBuff] 攻速 functionpar 缺少万分比');
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
return Math.max(0, Math.floor(nums[0]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 鏖战册:我方/对方攻速 */
|
|
|
|
|
export type BattleBuffBothAttackSpeedConfig = {
|
|
|
|
|
/** 我方攻速万分比 */
|
|
|
|
|
selfWan: number;
|
|
|
|
|
/** 对方攻速万分比 */
|
|
|
|
|
enemyWan: number;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 解析鏖战册 functionpar:我方,对方 */
|
|
|
|
|
export function parseBattleBuffBothAttackSpeedConfig(raw: string | number | null | undefined): BattleBuffBothAttackSpeedConfig | null {
|
|
|
|
|
const nums = parseFunctionParNums(raw);
|
|
|
|
|
if (nums.length < 2) {
|
|
|
|
|
console.log('[BattleBuff] 鏖战册 functionpar 需 2 段');
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
const selfWan = Math.max(0, Math.floor(nums[0]));
|
|
|
|
|
const enemyWan = Math.max(0, Math.floor(nums[1]));
|
|
|
|
|
if (selfWan <= 0 && enemyWan <= 0) return null;
|
|
|
|
|
return { selfWan, enemyWan };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 击杀额外金币:概率万分比(成功固定 +1) */
|
|
|
|
|
export function parseBattleBuffKillCoinProbWan(raw: string | number | null | undefined): number {
|
|
|
|
|
const nums = parseFunctionParNums(raw);
|
|
|
|
|
if (nums.length < 1) {
|
|
|
|
|
console.log('[BattleBuff] 赏金令 functionpar 缺少万分比');
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
return Math.max(0, Math.min(WAN_DENOM, Math.floor(nums[0])));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 倍增卡 / 属性卡产卡 / 宝石掉落 / 省金 / 滞行:单段万分比 */
|
|
|
|
|
export function parseBattleBuffWan(raw: string | number | null | undefined, label: string): number {
|
|
|
|
|
const nums = parseFunctionParNums(raw);
|
|
|
|
|
if (nums.length < 1) {
|
|
|
|
|
console.log(`[BattleBuff] ${label} functionpar 缺少万分比`);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
return Math.max(0, Math.floor(nums[0]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 基地加血整数 */
|
|
|
|
|
export function parseBattleBuffBaseHpBonus(raw: string | number | null | undefined): number {
|
|
|
|
|
const nums = parseFunctionParNums(raw);
|
|
|
|
|
if (nums.length < 1) {
|
|
|
|
|
console.log('[BattleBuff] 基地加血 functionpar 缺少数值');
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
return Math.max(0, Math.floor(nums[0]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 双生盾:我方/对方基地血 */
|
|
|
|
|
export type BattleBuffBothBaseHpConfig = {
|
|
|
|
|
/** 我方加血 */
|
|
|
|
|
selfHp: number;
|
|
|
|
|
/** 对方加血 */
|
|
|
|
|
enemyHp: number;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 解析双生盾 functionpar:我方,对方 */
|
|
|
|
|
export function parseBattleBuffBothBaseHpConfig(raw: string | number | null | undefined): BattleBuffBothBaseHpConfig | null {
|
|
|
|
|
const nums = parseFunctionParNums(raw);
|
|
|
|
|
if (nums.length < 2) {
|
|
|
|
|
console.log('[BattleBuff] 双生盾 functionpar 需 2 段');
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
const selfHp = Math.max(0, Math.floor(nums[0]));
|
|
|
|
|
const enemyHp = Math.max(0, Math.floor(nums[1]));
|
|
|
|
|
if (selfHp <= 0 && enemyHp <= 0) return null;
|
|
|
|
|
return { selfHp, enemyHp };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 寻金尺:解锁金币区间 */
|
|
|
|
|
export type BattleBuffUnlockCoinConfig = {
|
|
|
|
|
/** 最小金币(含) */
|
|
|
|
|
minCoin: number;
|
|
|
|
|
/** 最大金币(含) */
|
|
|
|
|
maxCoin: number;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 解析寻金尺 functionpar:min,max */
|
|
|
|
|
export function parseBattleBuffUnlockCoinConfig(raw: string | number | null | undefined): BattleBuffUnlockCoinConfig | null {
|
|
|
|
|
const nums = parseFunctionParNums(raw);
|
|
|
|
|
if (nums.length < 2) {
|
|
|
|
|
console.log('[BattleBuff] 寻金尺 functionpar 需 2 段');
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
const a = Math.max(0, Math.floor(nums[0]));
|
|
|
|
|
const b = Math.max(0, Math.floor(nums[1]));
|
|
|
|
|
if (a <= 0 && b <= 0) return null;
|
|
|
|
|
return { minCoin: Math.min(a, b), maxCoin: Math.max(a, b) };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 御雷珠(同旧九霄神雷 5 段) */
|
|
|
|
|
export type BattleBuffBaseThunderConfig = {
|
|
|
|
|
/** 单局最多触发次数 */
|
|
|
|
|
maxTriggers: number;
|
|
|
|
|
/** 小怪伤害比例万分比 */
|
|
|
|
|
smallDmgWan: number;
|
|
|
|
|
/** Boss 伤害比例万分比 */
|
|
|
|
|
bossDmgWan: number;
|
|
|
|
|
/** 触发距离(距终点格数) */
|
|
|
|
|
triggerCells: number;
|
|
|
|
|
/** 生效距离(从锚点格沿进路向出怪口后退格数,含锚点格) */
|
|
|
|
|
effectCells: number;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 解析御雷珠 functionpar:次数,小怪伤,Boss伤,触发格,生效格 */
|
|
|
|
|
export function parseBattleBuffBaseThunderConfig(raw: string | number | null | undefined): BattleBuffBaseThunderConfig | null {
|
|
|
|
|
const nums = parseFunctionParNums(raw);
|
|
|
|
|
if (nums.length < 5) {
|
|
|
|
|
console.log('[BattleBuff] 御雷珠 functionpar 需 5 段');
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
const maxTriggers = Math.floor(nums[0]);
|
|
|
|
|
const smallDmgWan = Math.max(0, Math.min(WAN_DENOM, Math.floor(nums[1])));
|
|
|
|
|
const bossDmgWan = Math.max(0, Math.min(WAN_DENOM, Math.floor(nums[2])));
|
|
|
|
|
const triggerCells = Math.floor(nums[3]);
|
|
|
|
|
const effectCells = Math.floor(nums[4]);
|
|
|
|
|
if (maxTriggers <= 0 || smallDmgWan <= 0 || bossDmgWan <= 0 || triggerCells <= 0 || effectCells <= 0) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return { maxTriggers, smallDmgWan, bossDmgWan, triggerCells, effectCells };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 金矿间隔秒 */
|
|
|
|
|
export function parseBattleBuffGoldMineIntervalSec(raw: string | number | null | undefined): number {
|
|
|
|
|
const nums = parseFunctionParNums(raw);
|
|
|
|
|
if (nums.length < 1) {
|
|
|
|
|
console.log('[BattleBuff] 金矿 functionpar 缺少秒数');
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
return Math.max(0, Math.floor(nums[0]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** targetpar 类型1:等级区间 */
|
|
|
|
|
export interface BattleBuffTargetLevelRange {
|
|
|
|
|
/** 最小等级(含) */
|
|
|
|
|
minLv: number;
|
|
|
|
|
/** 最大等级(含) */
|
|
|
|
|
maxLv: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 解析 Battlebuff.targetpar。
|
|
|
|
|
* 格式:类型,参数…;类型1=等级区间(min,max);类型2 预留。
|
|
|
|
|
*/
|
|
|
|
|
export class BattleBuffTargetParse {
|
|
|
|
|
/**
|
|
|
|
|
* 解析等级区间;非类型1 或非法时返回 null。
|
|
|
|
|
* @param raw targetpar
|
|
|
|
|
*/
|
|
|
|
|
static parseLevelRange(raw: string | null | undefined): BattleBuffTargetLevelRange | null {
|
|
|
|
|
const text = String(raw ?? '').trim();
|
|
|
|
|
if (!text) return null;
|
|
|
|
|
const nums = parseFunctionParNums(text);
|
|
|
|
|
if (nums.length < 1) return null;
|
|
|
|
|
const kind = Math.floor(nums[0]);
|
|
|
|
|
if (kind === 2) {
|
|
|
|
|
console.warn('[BattleBuff] targetpar 类型2尚未实现', raw);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
if (kind !== 1) {
|
|
|
|
|
console.warn('[BattleBuff] targetpar 未知类型', raw);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
if (nums.length < 3) {
|
|
|
|
|
console.warn('[BattleBuff] targetpar 类型1缺少等级区间', raw);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
const minLv = Math.floor(nums[1]);
|
|
|
|
|
const maxLv = Math.floor(nums[2]);
|
|
|
|
|
if (minLv <= 0 || maxLv < minLv) {
|
|
|
|
|
console.warn('[BattleBuff] targetpar 等级区间非法', raw);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return { minLv, maxLv };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 数字是否命中 target + targetpar。
|
|
|
|
|
* @param target 0/空=不过滤;1~5 五行;99 全五行
|
|
|
|
|
* @param targetpar 生效参数
|
|
|
|
|
* @param heroType 单位 herotype(1~5)
|
|
|
|
|
* @param level 单位等级
|
|
|
|
|
*/
|
|
|
|
|
static matches(target: number, targetpar: string | null | undefined, heroType: number, level: number): boolean {
|
|
|
|
|
const t = Math.floor(Number(target) || 0);
|
|
|
|
|
const ht = Math.floor(Number(heroType) || 0);
|
|
|
|
|
const lv = Math.floor(Number(level) || 0);
|
|
|
|
|
if (t === 0) return true;
|
|
|
|
|
if (t === 99) {
|
|
|
|
|
if (ht < 1 || ht > 5) return false;
|
|
|
|
|
} else if (t >= 1 && t <= 5) {
|
|
|
|
|
if (ht !== t) return false;
|
|
|
|
|
} else {
|
|
|
|
|
console.warn('[BattleBuff] target 非法', t);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
const range = this.parseLevelRange(targetpar);
|
|
|
|
|
if (!range) return !String(targetpar ?? '').trim();
|
|
|
|
|
return lv >= range.minLv && lv <= range.maxLv;
|
|
|
|
|
}
|
|
|
|
|
}
|