(function (root, factory) { const api = factory(); if (typeof module === 'object' && module.exports) module.exports = api; else root.DeltaArmor = api; })(typeof globalThis !== 'undefined' ? globalThis : this, function () { 'use strict'; const EPS = 1e-9; const RULE = Object.freeze({ id: 'USER-SHATTER-001', source: '../../assets/screenshots/碎甲一枪肉伤计算公式.png', definition: 'A=甲伤基值×对目标甲的衰减系数×甲伤距离保留倍率;B=肉伤基值×弹药肉伤倍率×部位倍率×肉伤距离保留倍率;C=本发命中前耐久;x=肉伤减伤比例', formula: 'C≥A: B×(1−x); C max || (strictlyPositive && value === 0)) throw new Error(label + '超出有效范围'); } function hit(bodyDamage, effectiveArmorDamage, armorBefore, reduction, covered) { checkNumber(bodyDamage, '肉伤', 0, Infinity, false); checkNumber(effectiveArmorDamage, '有效甲伤', 0, Infinity, false); checkNumber(armorBefore, '耐久', 0, Infinity, false); checkNumber(reduction, '减伤比例', 0, 1, false); if (!covered || armorBefore <= 0) { return { bodyDamage: bodyDamage, armorDamage: 0, armorAfter: armorBefore, protectedFraction: 0, branch: covered ? '无甲' : '未受保护部位' }; } const armorLost = Math.min(armorBefore, effectiveArmorDamage); if (effectiveArmorDamage === 0 || armorBefore >= effectiveArmorDamage) { return { bodyDamage: bodyDamage * (1 - reduction), armorDamage: armorLost, armorAfter: Math.max(0, armorBefore - armorLost), protectedFraction: 1, branch: effectiveArmorDamage === 0 ? '甲伤为0' : 'C≥A' }; } const fraction = armorBefore / effectiveArmorDamage; return { bodyDamage: fraction * bodyDamage * (1 - reduction) + (1 - fraction) * bodyDamage, armorDamage: armorLost, armorAfter: 0, protectedFraction: fraction, branch: 'C 0 && A === 0 && ammo.reduction === 1)) { return { A: A, B: B, metrics: metrics, trace: trace, stk: null, ttkMs: null, cost: null, breakShot: null, outcome: 'no_damage' }; } for (let n = 1; n <= 1000; n++) { const current = hit(B, A, armor, ammo.reduction, common.covered); const nextHealth = Math.max(0, health - current.bodyDamage); const broke = common.covered && armor > EPS && current.armorAfter <= EPS; if (broke && breakShot === null) breakShot = n; trace.push(Object.assign({ shot: n, timeMs: (n - 1) * 60000 / common.rpm, healthBefore: health, healthAfter: nextHealth, armorBefore: armor, brokenThisShot: broke }, current)); health = nextHealth; armor = current.armorAfter; if (health <= EPS) { outcome = 'killed'; break; } } const killed = outcome === 'killed'; const stk = killed ? trace.length : null; return { A: A, B: B, metrics: metrics, trace: trace, stk: stk, ttkMs: killed ? (stk - 1) * 60000 / common.rpm : null, cost: killed && ammo.price !== null ? stk * ammo.price : null, breakShot: breakShot, outcome: outcome }; } function defaultReduction(penetrationLevel, armorLevel) { const difference = penetrationLevel - armorLevel; return difference < 0 ? 1 : difference === 0 ? 0.5 : difference === 1 ? 0.25 : 0; } function scanDurability(common, ammoA, ammoB, maximum) { checkNumber(maximum, '耐久扫描上限', 1, 500, true); if (!Number.isInteger(maximum)) throw new Error('耐久扫描上限需为整数'); const points = []; const ranges = []; for (let armor = 0; armor <= maximum; armor++) { const input = Object.assign({}, common, { armor: armor }); const a = simulate(input, ammoA); const b = simulate(input, ammoB); const finite = a.stk !== null && b.stk !== null; const point = { armor: armor, aStk: a.stk, bStk: b.stk, aOutcome: a.outcome, bOutcome: b.outcome, ttkSavingMs: finite ? a.ttkMs - b.ttkMs : null, extraCost: a.cost !== null && b.cost !== null ? b.cost - a.cost : null, equalCostPriceRatio: finite ? a.stk / b.stk : null }; points.push(point); const previous = ranges[ranges.length - 1]; if (previous && previous.aStk === point.aStk && previous.bStk === point.bStk && previous.aOutcome === point.aOutcome && previous.bOutcome === point.bOutcome) previous.end = armor; else ranges.push(Object.assign({ start: armor, end: armor }, point)); } return { maximum: maximum, step: 1, points: points, ranges: ranges, scope: '只改变初始耐久;其余输入保持当前值。整数采样,不是精确连续阈值;扫描上限不是防具满耐久。' }; } return { rule: RULE, hit: hit, simulate: simulate, defaultReduction: defaultReduction, scanDurability: scanDurability }; });