Files
noname-shuying/mengsan/ui/intent-display.js
T
Diuse 82d80397b6 fixed # 新增羁绊系统
fixed # 新增遗物系统
fixed # 梦三模式下移除护甲上限限制
2026-09-30 14:33:17 +08:00

162 lines
7.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 只呈现已经选定的预测技能;不从手牌或原生技能猜测意图。
import { attackHitCount, previewAttackDamage } from "../battle/intent-damage.js";
import { isDeathBlowIntent } from "../battle/death-blow.js";
import { isStunIntent } from "../battle/stun-intent.js";
export function attackIconForDamage(damage) {
if (!Number.isInteger(damage) || damage < 0) {
throw new RangeError("预测伤害必须是非负整数");
}
if (damage < 5) return 1;
if (damage < 10) return 2;
if (damage < 20) return 3;
if (damage < 40) return 4;
return 5;
}
export function mountEnemyIntent(player, session, assetBase, document,
getTarget = () => null) {
const badge = document.createElement("div");
badge.className = "mengsan-intent-shuying";
badge.hidden = true;
badge.setAttribute("aria-live", "polite");
badge.setAttribute("aria-atomic", "true");
const damageLine = document.createElement("span");
damageLine.className = "mengsan-intent-damage-shuying";
const amount = document.createElement("span");
amount.className = "mengsan-intent-amount-shuying";
const icon = document.createElement("img");
icon.className = "mengsan-intent-icon-shuying";
icon.alt = "";
icon.decoding = "async";
damageLine.append(icon, amount);
const debuffLine = document.createElement("span");
debuffLine.className = "mengsan-intent-debuff-shuying";
const debuffIcon = document.createElement("img");
debuffIcon.className = "mengsan-intent-icon-shuying";
debuffIcon.alt = "";
debuffIcon.decoding = "async";
debuffIcon.src = `${assetBase}/assets/intent/Intent_debuff.png`;
const debuffAmount = document.createElement("span");
debuffLine.append(debuffIcon, debuffAmount);
const blockLine = document.createElement("span");
blockLine.className = "mengsan-intent-block-shuying";
const blockIcon = document.createElement("img");
blockIcon.className = "mengsan-intent-icon-shuying";
blockIcon.alt = "";
blockIcon.decoding = "async";
blockIcon.src = `${assetBase}/assets/intent/Intent_defend.png`;
const blockAmount = document.createElement("span");
blockLine.append(blockIcon, blockAmount);
const buffLine = document.createElement("span");
buffLine.className = "mengsan-intent-buff-shuying";
const buffIcon = document.createElement("img");
buffIcon.className = "mengsan-intent-icon-shuying";
buffIcon.alt = "";
buffIcon.decoding = "async";
buffIcon.src = `${assetBase}/assets/intent/Intent_buff.png`;
const buffAmount = document.createElement("span");
buffLine.append(buffIcon, buffAmount);
badge.append(damageLine, debuffLine, blockLine, buffLine);
player.appendChild(badge);
let currentIntent = null;
let timer;
session.ownResource(badge, () => {
clearInterval(timer);
badge.remove();
});
const refresh = () => {
const intent = currentIntent;
if (!intent) {
badge.hidden = true;
return;
}
if (isStunIntent(intent)) {
damageLine.hidden = false;
debuffLine.hidden = true;
blockLine.hidden = true;
buffLine.hidden = true;
amount.textContent = "";
const src = `${assetBase}/assets/intent/Intent_stun.png`;
if (icon.getAttribute("src") !== src) icon.src = src;
if (badge.getAttribute("aria-label") !== "Stunned") {
badge.setAttribute("aria-label", "Stunned");
}
badge.hidden = false;
return;
}
const hasDamage = intent.damage != null;
const deathBlow = isDeathBlowIntent(intent);
if (deathBlow && !hasDamage) {
throw new TypeError("濒死一击必须有伤害数值");
}
damageLine.hidden = !hasDamage;
const hasDebuff = typeof intent.debuff === "string" &&
Number.isInteger(intent.stacks) && intent.stacks > 0;
debuffLine.hidden = !hasDebuff;
if (hasDebuff) {
const debuffText = `×${intent.stacks}`;
if (debuffAmount.textContent !== debuffText) {
debuffAmount.textContent = debuffText;
}
}
const hasBlock = Number.isInteger(intent.block) && intent.block > 0;
const hasStrength = Number.isInteger(intent.strength) && intent.strength > 0;
blockLine.hidden = !hasBlock;
buffLine.hidden = !hasStrength;
if (hasBlock) blockAmount.textContent = String(intent.block);
if (hasStrength) {
buffAmount.textContent = `+${intent.strength}`;
}
const effects = [];
if (hasDamage) {
const hits = attackHitCount(intent);
if (deathBlow && hits !== 1) {
throw new RangeError("濒死一击只能造成一次伤害");
}
const target = getTarget();
const targets = Array.isArray(target) ? target : [target];
const damages = (targets.length ? targets : [null]).map(
current => previewAttackDamage(intent, player, current));
const damage = Math.max(...damages);
const minimum = Math.min(...damages);
const range = minimum === damage ? String(damage) :
`${minimum}~${damage}`;
const tier = deathBlow ? null :
attackIconForDamage(damage * hits);
const damageText = hits > 1 ? `${range}×${hits}` : range;
if (amount.textContent !== damageText) {
amount.textContent = damageText;
}
const src = deathBlow ?
`${assetBase}/assets/intent/Intent_death_blow.png` :
`${assetBase}/assets/intent/Intent_attack_${tier}.png`;
if (icon.getAttribute("src") !== src) icon.src = src;
effects.push(`${deathBlow ? "Death Blow" : "Attack"}: ${range} damage${hits > 1 ? ` × ${hits} hits` : ""}`);
}
if (hasDebuff) {
const label = intent.debuff === "脆弱" ? "Frail" :
intent.debuff === "易伤" ? "Vulnerable" : "Debuff";
effects.push(`${label} ×${intent.stacks}`);
}
if (hasBlock) effects.push(`Block ${intent.block}`);
if (hasStrength) effects.push(`Strength +${intent.strength}`);
if (!effects.length) {
damageLine.hidden = false;
amount.textContent = "";
const src = `${assetBase}/assets/intent/Intent_unknown.png`;
if (icon.getAttribute("src") !== src) icon.src = src;
effects.push("Unknown");
}
const label = effects.join(", ");
badge.title = "攻击与负面效果作用于所有存活敌对角色;伤害按各目标状态计算,护甲另行抵扣。";
if (badge.getAttribute("aria-label") !== label) badge.setAttribute("aria-label", label);
badge.hidden = false;
};
timer = setInterval(refresh, 100);
return intent => {
currentIntent = intent;
refresh();
};
}