mirror of
https://github.com/Shu-Ying/noname-shuying.git
synced 2026-10-25 17:20:01 +08:00
fixed # 更改个人HUD
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
import { collectCombatStatuses } from './status-model.js';
|
||||
import { createOverlayLayout } from './overlays.js';
|
||||
|
||||
// Native cards, players and controls stay under their original parents.
|
||||
export function mountBattleHUD(player, battle, env) {
|
||||
const { ui, get, _status, document, window, assetBase } = env;
|
||||
const arena = ui.arena;
|
||||
if (!arena || !ui.me || !ui.control || !battle.session.active) throw new Error('梦三 HUD 缺少原生界面容器');
|
||||
const handRoot = ui.me, controlRoot = ui.control;
|
||||
const overlays = createOverlayLayout(arena, handRoot, ui, player);
|
||||
const restores = [], styled = new WeakMap(), hiddenMarks = new Set();
|
||||
let disposed = false, frozen = false, frame = null, dialog = null, previousFocus = null, resize, observer;
|
||||
const ownClass = (node, name) => {
|
||||
if (!node || node.classList.contains(name)) return;
|
||||
node.classList.add(name); restores.push(() => node.classList.remove(name));
|
||||
};
|
||||
const style = (node, key, value) => {
|
||||
let values = styled.get(node);
|
||||
if (!values) styled.set(node, values = new Map());
|
||||
if (!values.has(key)) {
|
||||
const old = node.style.getPropertyValue(key), priority = node.style.getPropertyPriority(key);
|
||||
const record = { last: null }; values.set(key, record);
|
||||
restores.push(() => {
|
||||
if (node.style.getPropertyValue(key) !== record.last) return;
|
||||
if (old) node.style.setProperty(key, old, priority); else node.style.removeProperty(key);
|
||||
});
|
||||
}
|
||||
const record = values.get(key);
|
||||
if (record.requested !== value || node.style.getPropertyValue(key) !== record.last) {
|
||||
node.style.setProperty(key, value, 'important');
|
||||
record.requested = value;
|
||||
// CSSOM rounds long decimal pixel values; compare the actual stored value on release.
|
||||
record.last = node.style.getPropertyValue(key);
|
||||
}
|
||||
};
|
||||
const text = (node, value) => { if (node.textContent !== String(value)) node.textContent = String(value); };
|
||||
const create = (tag, className, parent, label) => {
|
||||
const node = document.createElement(tag); node.className = className;
|
||||
if (label != null) node.textContent = label;
|
||||
if (tag === 'button') node.type = 'button';
|
||||
parent?.appendChild(node); return node;
|
||||
};
|
||||
const plain = value => String(value || '').replace(/<[^>]*>/g, '');
|
||||
const nameOf = target => plain(get.translation(target.name || target.playerid || '角色'));
|
||||
const root = create('section', 'mengsan-hud-shuying', arena);
|
||||
root.setAttribute('aria-label', '梦三战斗操作区');
|
||||
const info = create('div', 'mengsan-hud-info-shuying', root);
|
||||
const name = create('strong', 'mengsan-hud-name-shuying', info, nameOf(player));
|
||||
const hp = create('span', 'mengsan-hud-hp-shuying', info);
|
||||
const healthTrack = create('div', 'mengsan-hud-health-track-shuying', info);
|
||||
healthTrack.setAttribute('aria-hidden', 'true');
|
||||
const healthFill = create('span', '', healthTrack);
|
||||
const block = create('span', 'mengsan-hud-block-shuying', info), energy = create('strong', 'mengsan-hud-energy-shuying', info);
|
||||
const center = create('div', 'mengsan-hud-center-shuying', root);
|
||||
const statuses = create('div', 'mengsan-hud-statuses-shuying', center);
|
||||
statuses.setAttribute('aria-label', '玩家当前状态');
|
||||
const link = create('link', '', null); link.rel = 'stylesheet'; link.href = `${assetBase}/ui/hud/style.css`;
|
||||
const closeDialog = () => {
|
||||
if (!dialog) return;
|
||||
const current = dialog; dialog = null;
|
||||
if (current.open) current.close(); current.remove();
|
||||
if (previousFocus?.isConnected) previousFocus.focus();
|
||||
};
|
||||
const openDetails = target => {
|
||||
if (disposed || !battle.session.active) return;
|
||||
closeDialog(); previousFocus = document.activeElement;
|
||||
dialog = create('dialog', 'mengsan-hud-dialog-shuying', document.body);
|
||||
const current = dialog;
|
||||
create('h2', '', dialog, `${nameOf(target)} · 当前状态`);
|
||||
const close = create('button', 'mengsan-hud-dialog-close-shuying', dialog, '关闭'); close.addEventListener('click', closeDialog);
|
||||
const entries = collectCombatStatuses(target, battle);
|
||||
for (const entry of entries) {
|
||||
const row = create('section', 'mengsan-hud-detail-row-shuying', dialog);
|
||||
create('strong', '', row, `${entry.name}${entry.value == null ? '' : ` ${entry.value}${entry.unit ? ` ${entry.unit}` : ''}`}`);
|
||||
create('p', '', row, entry.detail);
|
||||
}
|
||||
if (!entries.length) create('p', '', dialog, '暂无已登记状态。');
|
||||
// Unknown/native marks retain their engine intro and are never hidden by the HUD.
|
||||
dialog.addEventListener('cancel', event => { event.preventDefault(); closeDialog(); });
|
||||
dialog.addEventListener('close', () => { if (dialog === current) closeDialog(); });
|
||||
for (const type of ['pointerdown', 'pointerup', 'click', 'touchstart', 'touchend', 'keydown']) dialog.addEventListener(type, event => event.stopPropagation());
|
||||
dialog.showModal(); close.focus();
|
||||
};
|
||||
let statusSignature = null;
|
||||
function renderStatuses() {
|
||||
const entries = collectCombatStatuses(player, battle);
|
||||
const columns = layout === 'narrow' ? 1 : 2;
|
||||
const rows = Math.max(1, Math.floor((statuses.clientHeight || 80) / 38));
|
||||
const maximum = Math.max(0, columns * rows - (entries.length > columns * rows ? 1 : 0));
|
||||
const signature = JSON.stringify([maximum, entries]);
|
||||
if (signature !== statusSignature) {
|
||||
statusSignature = signature; statuses.replaceChildren();
|
||||
center.hidden = entries.length === 0;
|
||||
for (const entry of entries.slice(0, maximum)) {
|
||||
const button = create('button', 'mengsan-hud-status-shuying', statuses);
|
||||
button.setAttribute('aria-label', `${entry.name} ${entry.value ?? ''} ${entry.unit},查看状态详情`);
|
||||
if (entry.icon) {
|
||||
const image = create('img', '', button); image.src = `${assetBase}/assets/status/${entry.icon}`; image.alt = '';
|
||||
image.addEventListener('error', () => image.remove(), { once: true });
|
||||
}
|
||||
create('span', '', button, entry.name);
|
||||
if (entry.value != null) create('b', '', button, entry.value);
|
||||
button.addEventListener('click', () => openDetails(player));
|
||||
}
|
||||
if (entries.length > maximum) {
|
||||
const more = create('button', 'mengsan-hud-status-shuying', statuses, `+${entries.length - maximum}`);
|
||||
more.setAttribute('aria-label', '查看全部状态'); more.addEventListener('click', () => openDetails(player));
|
||||
}
|
||||
}
|
||||
const wanted = new Set();
|
||||
for (const target of [player]) {
|
||||
for (const entry of collectCombatStatuses(target, battle)) {
|
||||
for (const skill of entry.markSkills) {
|
||||
const mark = target.marks?.[skill]; if (mark?.classList) wanted.add(mark);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const mark of hiddenMarks) if (!wanted.has(mark)) { mark.classList.remove('mengsan-hud-represented-mark-shuying'); hiddenMarks.delete(mark); }
|
||||
for (const mark of wanted) if (!mark.classList.contains('mengsan-hud-represented-mark-shuying')) { mark.classList.add('mengsan-hud-represented-mark-shuying'); hiddenMarks.add(mark); }
|
||||
}
|
||||
let layout = null;
|
||||
const hudHeightFor = () => layout === 'short' ? 188 : layout === 'narrow' ? 288 : 274;
|
||||
const oldLayout = arena.getAttribute('data-mengsan-hud-layout');
|
||||
restores.push(() => {
|
||||
if (arena.getAttribute('data-mengsan-hud-layout') !== layout) return;
|
||||
if (oldLayout === null) arena.removeAttribute('data-mengsan-hud-layout'); else arena.setAttribute('data-mengsan-hud-layout', oldLayout);
|
||||
});
|
||||
function skinActor(target) {
|
||||
// Decade nests these in camp/hp wraps; direct-child .hp/.name selectors miss them.
|
||||
for (const key of ['campWrap', 'hpWrap', 'hp', 'identity', 'framebg', 'count', 'name', 'name2', 'nameol']) {
|
||||
ownClass(target.node?.[key], 'mengsan-hud-replaced-node-shuying');
|
||||
}
|
||||
ownClass(target.node?.avatar, 'mengsan-hud-portrait-shuying');
|
||||
ownClass(target.node?.equips, 'mengsan-hud-equips-shuying');
|
||||
if (target === player) ownClass(target.node?.equips, 'mengsan-hud-self-equips-shuying');
|
||||
}
|
||||
function syncActors() {
|
||||
const width = arena.clientWidth, height = arena.clientHeight;
|
||||
const narrow = width <= 600 && height >= 480;
|
||||
const short = height <= 500 && !narrow;
|
||||
layout = short ? 'short' : narrow ? 'narrow' : 'wide';
|
||||
if (arena.getAttribute('data-mengsan-hud-layout') !== layout) arena.setAttribute('data-mengsan-hud-layout', layout);
|
||||
const hudHeight = hudHeightFor();
|
||||
style(arena, '--mengsan-hud-height', `${hudHeight}px`);
|
||||
const large = width >= 1000;
|
||||
style(arena, '--mengsan-hud-left', short ? '216px' : narrow ? '116px' : large ? '352px' : '292px');
|
||||
style(arena, '--mengsan-hud-right', short ? '132px' : narrow ? '116px' : '184px');
|
||||
style(arena, '--mengsan-profile-info-left', short ? '108px' : narrow ? '22px' : large ? '200px' : '160px');
|
||||
style(arena, '--mengsan-profile-width', short ? '172px' : narrow ? '76px' : large ? '200px' : '130px');
|
||||
skinActor(player);
|
||||
// Inline important dimensions also beat skins with #arena > .player !important rules.
|
||||
const selfBottom = narrow ? 180 : 24;
|
||||
const selfHeight = short || narrow ? 104 : large ? 196 : 148;
|
||||
const infoTop = narrow ? hudHeight - selfBottom + 16 : hudHeight - selfBottom - selfHeight + (short ? 0 : 20);
|
||||
style(arena, '--mengsan-profile-info-top', `${infoTop}px`);
|
||||
for (const [key, value] of Object.entries({left: short ? '24px' : narrow ? '22px' : '40px', right:'auto', top:'auto',
|
||||
bottom:`${selfBottom}px`, width:short ? '76px' : narrow ? '76px' : large ? '144px' : '104px',
|
||||
height:`${selfHeight}px`, transform:'none'})) style(player, key, value);
|
||||
style(arena, '--mengsan-side-dock-bottom', `${selfBottom + selfHeight + 12}px`);
|
||||
style(arena, '--mengsan-relic-dock-bottom', `${selfBottom + selfHeight + (narrow ? -4 : 12)}px`);
|
||||
style(arena, '--mengsan-skill-ceiling', `${Math.min(hudHeight - 36, selfBottom + selfHeight)}px`);
|
||||
style(arena, '--mengsan-status-height', short ? '52px' : narrow ? '84px' : '160px');
|
||||
}
|
||||
function syncExternalEquipment() {
|
||||
const equips = player.node?.equips;
|
||||
if (!equips?.isConnected || equips.parentNode === player) return;
|
||||
// An optional skin can keep the same real equipment nodes in a separate arena wrapper.
|
||||
ownClass(ui.equipSolts, 'mengsan-hud-equip-host-shuying');
|
||||
const host = equips.offsetParent || arena, h = host.getBoundingClientRect(), a = arena.getBoundingClientRect(), p = player.getBoundingClientRect();
|
||||
const sx = h.width / host.offsetWidth || 1, sy = h.height / host.offsetHeight || sx;
|
||||
const arenaScale = a.width / arena.clientWidth || 1;
|
||||
const width = Math.max(0, p.width - 12 * arenaScale);
|
||||
const equipHeight = Math.min(100 * arenaScale, p.height * .58);
|
||||
for (const [key, value] of Object.entries({position:'absolute',left:`${(p.left + 6 * arenaScale - h.left) / sx}px`,right:'auto',bottom:'auto',
|
||||
top:`${(p.bottom - 6 * arenaScale - equipHeight - h.top) / sy}px`,width:`${width / sx}px`,height:`${equipHeight / sy}px`,'max-height':`${equipHeight / sy}px`})) style(equips, key, value);
|
||||
}
|
||||
function placeControl(node, bottom, maxHeight) {
|
||||
ownClass(node, 'mengsan-hud-external-control-shuying');
|
||||
const host = node.offsetParent || arena, a = arena.getBoundingClientRect(), h = host.getBoundingClientRect();
|
||||
const scale = h.width / host.offsetWidth || 1, arenaScale = a.width / arena.clientWidth || 1;
|
||||
const rail = layout === 'wide' ? 142 : layout === 'short' ? 112 : 96;
|
||||
for (const [key, value] of Object.entries({position:'absolute',left:'auto',top:'auto',transform:'none',
|
||||
right:`${(h.right - a.right + (layout === 'wide' ? 24 : 16) * arenaScale) / scale}px`, bottom:`${(h.bottom - a.bottom + bottom * arenaScale) / scale}px`,
|
||||
width:`${rail * arenaScale / scale}px`, 'max-height':`${maxHeight * arenaScale / scale}px`})) style(node, key, value);
|
||||
}
|
||||
function syncControls() {
|
||||
// The actual action controls stay horizontal above the hand; skill references get their own rail.
|
||||
const candidates = [...new Set([ui.skills, ui.skills2, ui.skills3, ui.skillControl, ui.gskillControl])]
|
||||
.filter(node => node?.isConnected && node !== ui.control);
|
||||
const external = candidates.filter(node => !candidates.some(other => other !== node && other.contains(node)));
|
||||
const hudHeight = hudHeightFor();
|
||||
const rail = layout === 'wide' ? 142 : layout === 'short' ? 112 : 96;
|
||||
const actionBottom = layout === 'narrow' ? hudHeight - 48 : hudHeight + 8;
|
||||
const compactActions = arena.clientWidth < 1000;
|
||||
for (const [key,value] of Object.entries({position:'absolute',left:compactActions?'16px':'var(--mengsan-hud-left)',right:compactActions?'16px':'var(--mengsan-hud-right)',top:'auto',bottom:`${actionBottom}px`,width:'auto','max-height':'none',transform:'none'})) style(ui.control,key,value);
|
||||
for (const group of ui.control.children) if (group.classList.contains('control')) {
|
||||
if (external.includes(group)) {
|
||||
ownClass(group,'mengsan-hud-native-skills-shuying');
|
||||
for (const button of group.children) if (button.tagName === 'DIV') style(button, 'margin', '0px');
|
||||
continue;
|
||||
}
|
||||
for (const [key, value] of Object.entries({position:'relative',left:'auto',right:'auto',top:'auto',bottom:'auto',transform:'none'})) style(group, key, value);
|
||||
}
|
||||
let bottom = 20;
|
||||
const ceiling = parseFloat(arena.style.getPropertyValue('--mengsan-skill-ceiling'));
|
||||
external.forEach((node, index) => {
|
||||
if (node === ui.skillControl || node === ui.gskillControl) ownClass(node, 'mengsan-hud-skin-skills-shuying');
|
||||
placeControl(node, bottom, Math.max(24, (ceiling - bottom - 6 * (external.length - index - 1)) / (external.length - index)));
|
||||
bottom += node.offsetHeight + 6;
|
||||
});
|
||||
for (const node of [ui.confirm,_status.event?.endButton]) {
|
||||
if (!node?.isConnected || ui.control.contains(node)) continue;
|
||||
ownClass(node,'mengsan-hud-external-action-shuying');
|
||||
const host=node.offsetParent||arena,a=arena.getBoundingClientRect(),h=host.getBoundingClientRect();
|
||||
const scale=h.width/host.offsetWidth||1,zoom=a.width/arena.clientWidth||1;
|
||||
const center=(parseFloat(arena.style.getPropertyValue('--mengsan-hud-left'))+arena.clientWidth-parseFloat(arena.style.getPropertyValue('--mengsan-hud-right')))/2;
|
||||
const side=node===ui.confirm?-1:1;
|
||||
for(const [key,value] of Object.entries({position:'absolute',left:`${(a.left+center*zoom-h.left)/scale+side*80}px`,right:'auto',top:'auto',bottom:`${(h.bottom-a.bottom+actionBottom*zoom)/scale}px`,width:`${Math.min(rail,140)}px`,transform:'translateX(-50%)'}))style(node,key,value);
|
||||
}
|
||||
}
|
||||
function syncNotifications() {
|
||||
const maxTop = arena.clientHeight - hudHeightFor() - 48;
|
||||
// Decade positions string popups at player.top + 80, which now intersects our footer.
|
||||
for (const popup of arena.querySelectorAll(':scope > .damage.skill-popup')) {
|
||||
if(!popup.classList.contains('mengsan-hud-notification-shuying') &&
|
||||
(Math.abs(popup.offsetLeft-player.offsetLeft)>1 || Math.abs(popup.offsetTop-player.offsetTop-80)>1))continue;
|
||||
ownClass(popup, 'mengsan-hud-notification-shuying');
|
||||
if (popup.offsetTop > maxTop) style(popup, 'top', `${Math.max(0, maxTop)}px`);
|
||||
const maxLeft = Math.max(0, arena.clientWidth - 160);
|
||||
style(popup, 'left', `${Math.max(12, Math.min(maxLeft, popup.offsetLeft))}px`);
|
||||
}
|
||||
}
|
||||
function refresh() {
|
||||
if (disposed || frozen) return;
|
||||
if (!battle.session.active || !arena.isConnected || !handRoot.isConnected || ui.me !== handRoot || ui.control !== controlRoot) { dispose(); return; }
|
||||
text(name, nameOf(player)); text(hp, `生命 ${player.hp} / ${player.maxHp}`); text(block, `格挡 ${player.hujia || 0}`);
|
||||
healthFill.style.width = `${Math.max(0, Math.min(100, player.maxHp > 0 ? player.hp / player.maxHp * 100 : 0))}%`;
|
||||
text(energy, `费用 ${player.storage.mengsanEnergy_shuying ?? 0} / ${player.storage.mengsanMaxEnergy_shuying ?? 0}`);
|
||||
syncActors(); syncExternalEquipment(); renderStatuses(); syncControls(); syncNotifications();
|
||||
overlays.refresh({ footerHeight:hudHeightFor(), status:_status });
|
||||
}
|
||||
function schedule() {
|
||||
if (disposed || frozen || document.hidden || frame !== null) return;
|
||||
frame = window.requestAnimationFrame(() => { frame = null; refresh(); });
|
||||
}
|
||||
const timer = window.setInterval(schedule, 200);
|
||||
function freeze() {
|
||||
if(disposed || frozen)return;
|
||||
frozen=true;window.clearInterval(timer);
|
||||
if(frame!==null){window.cancelAnimationFrame(frame);frame=null;}
|
||||
observer?.disconnect();resize?.disconnect();closeDialog();overlays.dispose();
|
||||
ownClass(arena,'mengsan-hud-frozen-shuying');
|
||||
}
|
||||
function dispose() {
|
||||
if (disposed) return; disposed = true;
|
||||
window.clearInterval(timer); if (frame !== null) window.cancelAnimationFrame(frame);
|
||||
observer?.disconnect(); resize?.disconnect(); window.removeEventListener('resize', schedule);
|
||||
document.removeEventListener('visibilitychange', schedule);
|
||||
overlays.dispose();
|
||||
closeDialog();
|
||||
for (const mark of hiddenMarks) mark.classList.remove('mengsan-hud-represented-mark-shuying'); hiddenMarks.clear();
|
||||
for (const restore of restores.reverse()) restore();
|
||||
root.remove(); link.remove();
|
||||
}
|
||||
ownClass(document.body, 'mengsan-hud-active-shuying'); ownClass(arena, 'mengsan-hud-arena-shuying');
|
||||
ownClass(ui.me, 'mengsan-hud-hand-shuying'); ownClass(ui.control, 'mengsan-hud-controls-shuying');
|
||||
ownClass(ui.mebg, 'mengsan-hud-mebg-shuying'); ownClass(player, 'mengsan-hud-self-shuying');
|
||||
// Panel events must not reach native selection/drag handlers.
|
||||
for (const type of ['pointerdown', 'pointerup', 'click', 'touchstart', 'touchend', 'contextmenu']) root.addEventListener(type, event => event.stopPropagation());
|
||||
link.addEventListener('load', schedule);
|
||||
link.addEventListener('error', () => { env.log?.('梦三 HUD 样式加载失败,保留原生布局'); dispose(); });
|
||||
document.head.appendChild(link);
|
||||
observer = new window.MutationObserver(schedule); observer.observe(ui.control, { childList: true, subtree: true });
|
||||
observer.observe(arena, { childList: true });
|
||||
if(ui.window && ui.window!==arena) observer.observe(ui.window,{childList:true});
|
||||
if (window.ResizeObserver) { resize = new window.ResizeObserver(schedule); resize.observe(arena); }
|
||||
window.addEventListener('resize', schedule); document.addEventListener('visibilitychange', schedule);
|
||||
const api = { refresh, freeze, dispose }; battle.session.ownResource(api, dispose); refresh(); return api;
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// Position existing native prompts and popped descriptions; never recreate their content or handlers.
|
||||
export function createOverlayLayout(arena, hand, ui, player) {
|
||||
const records = new Map();
|
||||
const roots = new Set([ui.window, arena].filter(Boolean));
|
||||
const popped = () => new Set([...roots].flatMap(root => [...root.querySelectorAll(':scope > .dialog.popped')]));
|
||||
const markPopups = new WeakSet();
|
||||
let activeMarkPopup = null, openingMark = false, disposed = false, options = null;
|
||||
// Native mark intros do not reject repeated clicks themselves. Observe their real
|
||||
// popup after the native handler, and block duplicate opens without replacing it.
|
||||
const guardMark = event => {
|
||||
const target = event.target.closest?.('.mark');
|
||||
if (!target || !player.contains(target)) return;
|
||||
if (openingMark || activeMarkPopup?.isConnected) {
|
||||
event.preventDefault(); event.stopImmediatePropagation(); return;
|
||||
}
|
||||
const before = popped();
|
||||
openingMark = true;
|
||||
arena.ownerDocument.defaultView.queueMicrotask(() => {
|
||||
openingMark = false;
|
||||
if (disposed) return;
|
||||
activeMarkPopup = [...popped()].find(node => !before.has(node)) || null;
|
||||
if (activeMarkPopup) { markPopups.add(activeMarkPopup); if (options) api.refresh(options); }
|
||||
});
|
||||
};
|
||||
const markEvents = ['click', 'touchend', 'contextmenu'];
|
||||
for (const type of markEvents) player.addEventListener(type, guardMark, true);
|
||||
function release(node, record) {
|
||||
for (const [key, field] of record.styles) {
|
||||
if (node.style.getPropertyValue(key) !== field.last) continue;
|
||||
if (field.before) node.style.setProperty(key,field.before,field.priority); else node.style.removeProperty(key);
|
||||
}
|
||||
for (const [name, had] of record.classes) if (!had) node.classList.remove(name);
|
||||
records.delete(node);
|
||||
}
|
||||
function mark(node, name) {
|
||||
let record=records.get(node);
|
||||
if(!record) {record={styles:new Map(),classes:new Map()};records.set(node,record);}
|
||||
if(!record.classes.has(name))record.classes.set(name,node.classList.contains(name));
|
||||
node.classList.add(name);return record;
|
||||
}
|
||||
function style(node,key,value) {
|
||||
const record=records.get(node);
|
||||
if(!record.styles.has(key))record.styles.set(key,{before:node.style.getPropertyValue(key),priority:node.style.getPropertyPriority(key)});
|
||||
const field=record.styles.get(key);
|
||||
if(field.requested!==value||node.style.getPropertyValue(key)!==field.last)node.style.setProperty(key,value,'important');
|
||||
field.requested=value;field.last=node.style.getPropertyValue(key);
|
||||
}
|
||||
function position(node,x,y,width,maxHeight) {
|
||||
for(const [key,value] of Object.entries({position:'absolute',right:'auto',bottom:'auto',transform:'none',margin:'0px',width:`${width}px`,'max-height':`${maxHeight}px`}))style(node,key,value);
|
||||
const host=node.offsetParent||arena.ownerDocument.documentElement,h=host.getBoundingClientRect();
|
||||
const sx=h.width/host.offsetWidth||1,sy=h.height/host.offsetHeight||sx;
|
||||
style(node,'left',`${(x-h.left)/sx-host.clientLeft+host.scrollLeft}px`);
|
||||
style(node,'top',`${(y-h.top)/sy-host.clientTop+host.scrollTop}px`);
|
||||
}
|
||||
const api = {
|
||||
refresh(nextOptions) {
|
||||
options = nextOptions;
|
||||
const {footerHeight,status} = nextOptions;
|
||||
const used=new Set(),a=arena.getBoundingClientRect(),zoom=a.width/arena.clientWidth||1;
|
||||
const left=parseFloat(arena.style.getPropertyValue('--mengsan-hud-left'))||0;
|
||||
const right=parseFloat(arena.style.getPropertyValue('--mengsan-hud-right'))||0;
|
||||
const x=a.left+left*zoom,width=Math.max(100,arena.clientWidth-left-right);
|
||||
const controlTop=ui.control.getBoundingClientRect().top;
|
||||
const prompts=new Set(arena.querySelectorAll(':scope > .hand-tip,:scope > .hand-tip-info'));
|
||||
const dialog=ui.dialog;
|
||||
if(dialog?.isConnected && !dialog.classList.contains('popped') && !dialog.classList.contains('fullheight') && !dialog.forcebutton && !dialog.buttons?.length && !dialog.querySelector('.button,.card,.player,input,textarea,select') && status.event?.player===player)prompts.add(dialog);
|
||||
// The event owns simple native prompt dialogs too, without a global game dependency.
|
||||
const eventDialog=status.event?.dialog;
|
||||
if(eventDialog?.isConnected && eventDialog===dialog && !eventDialog.buttons?.length && !eventDialog.forcebutton && !eventDialog.querySelector('.button,.card,.player,input,textarea,select'))prompts.add(eventDialog);
|
||||
let promptBottom=Math.min(controlTop-10*zoom,a.bottom-(footerHeight+54)*zoom);
|
||||
for(const node of prompts) {
|
||||
if(node.hidden || node.style.display==='none')continue;
|
||||
mark(node,'mengsan-hud-prompt-shuying');used.add(node);
|
||||
position(node,x,0,width,arena.clientHeight<=500?34:64);
|
||||
const height=node.getBoundingClientRect().height;
|
||||
position(node,x,Math.max(a.top+50*zoom,promptBottom-height),width,arena.clientHeight<=500?34:64);
|
||||
promptBottom=node.getBoundingClientRect().top-6*zoom;
|
||||
}
|
||||
const handBox=hand.getBoundingClientRect();
|
||||
const handTop=Math.min(handBox.top,...[...hand.querySelectorAll('.handcards>.card')].map(node=>node.getBoundingClientRect().top));
|
||||
const popups=popped();
|
||||
let hasPopup=false;
|
||||
for(const node of popups) {
|
||||
const rect=node.getBoundingClientRect();
|
||||
if(!markPopups.has(node) && !records.has(node) && !(rect.bottom>handTop && rect.left<handBox.right && rect.right>handBox.left))continue;
|
||||
mark(node,'mengsan-hud-card-intro-shuying');used.add(node);hasPopup=true;
|
||||
const popupWidth=Math.min(320,Math.max(180,rect.width/zoom),arena.clientWidth-24);
|
||||
const maxHeight=Math.max(64,(promptBottom-a.top)/zoom-12);
|
||||
const center=Math.max(a.left+12*zoom,Math.min(a.right-(popupWidth+12)*zoom,markPopups.has(node)?x:rect.left));
|
||||
position(node,center,a.top+12*zoom,popupWidth,maxHeight);
|
||||
const height=node.getBoundingClientRect().height;
|
||||
position(node,center,Math.max(a.top+12*zoom,promptBottom-height-8*zoom),popupWidth,maxHeight);
|
||||
}
|
||||
if(hasPopup)for(const root of roots)for(const node of root.querySelectorAll(':scope > .poplayer')) {mark(node,'mengsan-hud-intro-layer-shuying');style(node,'z-index','89');used.add(node);}
|
||||
for(const [node,record] of records)if(!node.isConnected||!used.has(node))release(node,record);
|
||||
},
|
||||
dispose(){disposed=true;for(const type of markEvents)player.removeEventListener(type,guardMark,true);for(const [node,record]of records)release(node,record);},
|
||||
};
|
||||
return api;
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import { readCombatStatusSources } from '../../battle/status-sources.js';
|
||||
import { slowPercent } from '../../monsters/effigy-slow.js';
|
||||
|
||||
// The descriptions describe Mengsan's current rules, not an imported Wiki engine.
|
||||
const storageStatuses = [
|
||||
['vulnerable', '易伤', 'mengsanVulnerable_shuying', '回合', '受到攻击伤害增加;剩余自身回合数,倍率按攻击来源及当前效果结算。', 'mengsan_vulnerable_shuying'],
|
||||
['weak', '虚弱', 'mengsanWeak_shuying', '回合', '攻击伤害减少25%;纸鹤对敌人采用40%减伤。自身回合结束减少。', 'mengsan_weak_shuying'],
|
||||
['frail', '脆弱', 'mengsanFrail_shuying', '回合', '获得格挡减少25%,向下取整;自身回合结束减少。', 'mengsan_frail_shuying'],
|
||||
['artifact', '人工制品', 'mengsanArtifact_shuying', '次', '每次抵消一次符合规则的负面状态施加,不抵消伤害。', 'mengsan_artifact_shuying'],
|
||||
['constrict', '紧缠', 'mengsanConstrict_shuying', '伤害', '自身回合结束受到等量非攻击伤害;来源死亡解除。', 'mengsan_constrict_shuying'],
|
||||
['tangled', '缠结', 'mengsanTangled_shuying', '回合', '攻击牌费用增加1,持续时间可叠加,加费不叠加。', 'mengsan_tangled_shuying'],
|
||||
['shrink', '缩小', 'mengsanShrink_shuying', '', '攻击伤害减少30%,不重复叠加;来源死亡解除。', 'mengsan_shrink_shuying'],
|
||||
['plow', '横冲直撞', 'mengsanPlow_shuying', '生命阈值', '受到伤害后生命不高于该阈值时击晕,清零力量并进入第二阶段。', 'mengsan_plow_shuying'],
|
||||
['slippery', '滑溜', 'mengsanSlippery_shuying', '次', '下一次实际生命损失限制为1,完全格挡不消耗。', 'mengsan_slippery_shuying'],
|
||||
['territorial', '领地意识', 'mengsanTerritorial_shuying', '力量', '自身回合结束获得等量力量。', 'mengsan_territorial_shuying'],
|
||||
['infested', '寄生物', 'mengsanInfested_shuying', '召唤数', '死亡后立即召唤扭动虫,必须消灭召唤单位才能获胜。', 'mengsan_infested_shuying'],
|
||||
];
|
||||
// [display name, icon, value meaning, description]; only actual effect fields are read.
|
||||
const effects = {
|
||||
turnStrength: ['恶魔形态', 'demon_form', '力量', '自身回合开始获得力量。'],
|
||||
turnEnergy: ['每回合费用', 'demesne', '费用', '自身回合开始额外获得费用。'],
|
||||
turnBlock: ['绯红披风', 'crimson_mantle', '格挡', '自身回合开始获得格挡;失血部分另列。'],
|
||||
turnLoseHp: ['披风失血', 'crimson_mantle', '生命', '自身回合开始失去生命。'],
|
||||
infernoLoseHp: ['狱火失血', 'inferno', '生命', '自身回合开始失去生命。'],
|
||||
lossDamage: ['狱火', 'inferno', '伤害', '自身回合失去生命时,对所有敌人造成伤害。'],
|
||||
lossStrength: ['撕裂', 'rupture', '力量', '自身回合失去生命时获得力量。'],
|
||||
vulnerableBonus: ['易伤增幅', 'vicious', '倍率加成', '对易伤敌人的伤害倍率额外加成。'],
|
||||
attackBlock: ['狂怒', 'rage', '格挡', '本回合每打出一张攻击牌获得格挡。'],
|
||||
retaliation: ['火焰屏障', 'flame_barrier', '伤害', '本回合受到攻击后反击。'],
|
||||
colossus: ['巨像', 'colossus', '', '本回合受到易伤敌人的攻击伤害减少50%。'],
|
||||
freeAttack: ['免费攻击', 'free_attack', '', '下一张符合规则的攻击牌免费。'],
|
||||
noDraw: ['不可抽牌', 'no_draw', '', '本回合不能继续抽牌。'],
|
||||
noEnergy: ['无法获得费用', 'no_energy_gain', '', '本回合不能再获得费用。'],
|
||||
doubleTap: ['连环拳', 'one_two_punch', '张', '本回合接下来的攻击牌额外打出一次。'],
|
||||
plating: ['覆甲', 'plating', '格挡', '回合末获得等量格挡;自身回合开始减少1层。'],
|
||||
exhaustBlock: ['无惧疼痛', 'feel_no_pain', '格挡', '卡牌消耗后获得格挡。'],
|
||||
exhaustDraw: ['黑暗之拥', 'dark_embrace', '张', '卡牌消耗后抽牌。'],
|
||||
vulnerableDraw: ['凶恶', 'vicious', '张', '每次成功施加易伤后抽牌。'],
|
||||
juggle: ['杂耍', 'juggling', '', '自身回合第三张攻击牌复制至手牌。'],
|
||||
endAttack: ['惊逃', 'stampede', '张', '回合结束随机免费打出攻击手牌。'],
|
||||
retrieveAttack: ['好勇斗狠', 'aggression', '张', '自身回合开始从弃牌堆获得攻击牌并强化。'],
|
||||
bodyguard: ['肉盾', 'tank', '', '自身承受敌人双倍伤害,其他友方受到敌人的伤害减半。'],
|
||||
autoStrike: ['地狱狂徒', 'hellraiser', '', '抽到杀或名称含打击的攻击牌时自动打出。'],
|
||||
firstBlock: ['不动', 'unmovable', '张', '每个自身回合首批获得格挡的卡牌,格挡翻倍。'],
|
||||
blockDamage: ['势不可当', 'juggernaut', '伤害', '获得格挡时对随机敌人造成伤害。'],
|
||||
barricade: ['壁垒', 'barricade', '', '自身回合开始保留格挡。'],
|
||||
corruption: ['腐化', 'corruption', '', '技能牌费用变为0,打出后消耗。'],
|
||||
dexterity: ['敏捷', 'dexterity', '格挡', '影响从卡牌中获得的格挡。'],
|
||||
noCardBlock: ['不可格挡', 'no_block', '回合', '无法从卡牌中获得格挡。'],
|
||||
intangible: ['无实体', 'intangible', '回合', '按当前规则限制伤害和生命损失。'],
|
||||
gambit: ['孤注一掷', 'the_gambit', '', '受到未被格挡的攻击伤害时死亡。'],
|
||||
retain: ['保留手牌', 'retain_hand', '', '本回合保留手牌。'],
|
||||
panache: ['神气制胜', 'panache', '伤害', '自身回合每打出5张牌,对所有敌人造成伤害。'],
|
||||
defendBonus: ['勒紧', 'fasten', '格挡', '防御牌额外获得格挡。'],
|
||||
turnVigor: ['准备时间', 'prep_time', '活力', '自身回合开始获得活力。'],
|
||||
automation: ['自动化', 'automation', '费用', '抽牌达到计数要求后获得费用。'],
|
||||
powerDiscount: ['好奇', 'curious', '费用', '能力牌费用降低。'],
|
||||
boulder: ['滚石', 'rolling_boulder', '伤害', '回合开始对所有敌人造成伤害,随后伤害增加5。'],
|
||||
nostalgia: ['怀旧', 'nostalgia', '', '每回合首张攻击或技能牌放回抽牌堆顶。'],
|
||||
entropy: ['熵', 'entropy', '', '自身回合开始变化手牌。'],
|
||||
beacon_of_hope: ['希望灯塔', 'beacon_of_hope', '', '获得格挡时向其他友方提供格挡。'],
|
||||
mayhem: ['乱战', 'mayhem', '', '自身回合开始自动打出抽牌堆顶的牌。'],
|
||||
calamity: ['劫难', 'calamity', '', '打出攻击牌时生成随机攻击牌。'],
|
||||
};
|
||||
const activeValue = value => value === true || typeof value === 'number' && Number.isFinite(value) && value !== 0;
|
||||
const number = value => typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
||||
const make = (id, name, icon, value, unit, detail, markSkills = []) => ({ id, name,
|
||||
icon: icon ? `${icon}_power.png` : null, value: value === true ? null : value, unit, detail, markSkills });
|
||||
|
||||
export const statusIconNames = [...new Set(['strength', 'ringing', 'slow', 'the_bomb', 'vigor', 'draw_cards_next_turn', 'energy_next_turn', 'block_next_turn',
|
||||
...storageStatuses.map(row => row[0]), ...Object.values(effects).map(row => row[1])])].map(id => `${id}_power.png`);
|
||||
|
||||
export function collectCombatStatuses(player, battle) {
|
||||
if (!battle?.session?.active) return [];
|
||||
const storage = player.storage || {}, result = [], sources = readCombatStatusSources(player, battle.session);
|
||||
const penalty = sources.reduce((n, entry) => n + number(entry.state.negativeStrength), 0);
|
||||
const permanent = number(storage.mengsanStrength_shuying), temporary = number(storage.mengsanSetupStrength_shuying);
|
||||
if (permanent || temporary || penalty) result.push(make('strength', '力量', 'strength', permanent + temporary - penalty, '伤害',
|
||||
`常驻力量 ${permanent};临时力量 ${temporary};本回合减益 ${penalty}。不同临时来源按各自原规则到期。`,
|
||||
['mengsan_raider_strength_shuying', 'mengsan_setup_strength_shuying']));
|
||||
for (const [id, name, key, unit, detail, skill] of storageStatuses) {
|
||||
if (activeValue(storage[key])) result.push(make(id, name, id, storage[key], unit, detail, [skill]));
|
||||
}
|
||||
if (storage.mengsanRinging_shuying || player.hasSkill?.('mengsan_ringing_shuying')) result.push(make('ringing', '昏眩', 'ringing',
|
||||
`${Math.min(1, number(storage.mengsanRingingUsed_shuying))}/1`, '主动出牌', '本次自身回合至多主动使用1张牌,不限制响应;回合结束解除。', ['mengsan_ringing_shuying']));
|
||||
if (player.hasSkill?.('mengsan_slow_shuying')) result.push(make('slow', '缓慢', 'slow', slowPercent(player), '%',
|
||||
`缓慢 ${number(storage.mengsanSlow_shuying)} 层;友方回合每打出一张牌,受到的攻击牌伤害增加层数×10%;换回合计数重置。`, ['mengsan_slow_shuying']));
|
||||
for (const { id, state } of sources) {
|
||||
for (const zone of ['powers', 'turn', 'defense']) {
|
||||
for (const [key, value] of Object.entries(state[zone] || {})) {
|
||||
const definition = effects[key];
|
||||
if (!definition || !activeValue(value)) continue;
|
||||
const [name, icon, unit, detail] = definition;
|
||||
result.push(make(`${id}:${zone}:${key}`, name, icon, key === 'vulnerableBonus' ? `${Math.round(value * 100)}%` : value, unit, detail,
|
||||
id === 'ironclad' ? ['mengsan_ic_powers_shuying'] : []));
|
||||
}
|
||||
}
|
||||
if (activeValue(state.vigor)) result.push(make(`${id}:vigor`, '活力', 'vigor', state.vigor, '伤害', '下一张攻击牌获得额外伤害。'));
|
||||
(state.bombs || []).forEach((bomb, index) => result.push(make(`${id}:bomb:${index}`, '炸弹', 'the_bomb', bomb.turns, '回合',
|
||||
`此实例倒计时结束后对所有敌人造成${bomb.damage}点伤害;各炸弹独立结算。`)));
|
||||
(state.nextTurn || []).forEach((next, index) => {
|
||||
for (const [key, name, icon, unit] of [['draw', '下回合抽牌', 'draw_cards_next_turn', '张'], ['energy', '下回合费用', 'energy_next_turn', '费用'], ['block', '下回合格挡', 'block_next_turn', '格挡']]) {
|
||||
if (activeValue(next[key])) result.push(make(`${id}:next:${index}:${key}`, name, icon, next[key], unit,
|
||||
`自身回合开始获得;${next.remaining ? `剩余${next.remaining}次。` : '此实例结算一次。'}`));
|
||||
}
|
||||
});
|
||||
}
|
||||
if (battle.relics?.displayState && battle.owner === player) {
|
||||
const relic = battle.relics.displayState();
|
||||
for (const [key, name, icon, unit, detail] of [
|
||||
['dexterity', '遗物敏捷', 'dexterity', '格挡', '遗物提供的卡牌格挡修正。'],
|
||||
['plating', '遗物覆甲', 'plating', '格挡', '遗物提供的回合末格挡。'],
|
||||
['nextEnergy', '下回合费用', 'energy_next_turn', '费用', '遗物将在下个自身回合提供费用。'],
|
||||
['nextBlock', '下回合格挡', 'block_next_turn', '格挡', '遗物将在下个自身回合提供格挡。']]) {
|
||||
if (activeValue(relic[key])) result.push(make(`relic:${key}`, name, icon, relic[key], unit, detail));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/* Presentation only: native players, cards and controls keep their handlers. */
|
||||
.mengsan-hud-arena-shuying { --mengsan-hud-paper:#edf2f7;--mengsan-hud-gold:#dcc18e;--mengsan-hud-raised:transparent;--mengsan-hud-stroke:rgba(240,240,240,.35);overflow:clip!important; }
|
||||
.mengsan-hud-hand-shuying,.mengsan-hud-controls-shuying,.mengsan-hud-external-control-shuying,.mengsan-hud-self-shuying,.mengsan-hud-actor-shuying { transition:none!important; }
|
||||
.mengsan-hud-native-skills-shuying,.mengsan-hud-native-skills-shuying>div,.mengsan-hud-skin-skills-shuying>.enable,.mengsan-hud-skin-skills-shuying>.trigger,.mengsan-hud-skin-skills-shuying .skillitem { transition:none!important; }
|
||||
.mengsan-hud-shuying { position:absolute;left:0;right:0;bottom:0;height:var(--mengsan-hud-height,274px);z-index:20;box-sizing:border-box;pointer-events:none;color:var(--mengsan-hud-paper);background:transparent;border:0;border-radius:0;backdrop-filter:none;box-shadow:none;text-shadow:0 1px 3px #000,0 0 6px #000c;font:13px/1.4 "Microsoft YaHei",sans-serif; }
|
||||
/* Transparent layout only: no footer slab, tint, outline or blur. */
|
||||
.mengsan-hud-shuying::before,.mengsan-hud-shuying::after { display:none; }
|
||||
.mengsan-hud-shuying *,.mengsan-hud-dialog-shuying * { box-sizing:border-box; }
|
||||
.mengsan-hud-shuying div,.mengsan-hud-shuying span,.mengsan-hud-shuying strong,.mengsan-hud-shuying button,.mengsan-hud-dialog-shuying section,.mengsan-hud-dialog-shuying p { position:relative;display:block;float:none; }
|
||||
.mengsan-hud-shuying [hidden],.mengsan-unit-status-shuying[hidden] { display:none!important; }
|
||||
.mengsan-hud-shuying .mengsan-hud-info-shuying,.mengsan-hud-shuying .mengsan-hud-center-shuying { position:absolute; }
|
||||
.mengsan-hud-shuying button,.mengsan-unit-status-shuying,.mengsan-hud-dialog-shuying button { color:var(--mengsan-hud-paper,#edf2f7);background:var(--mengsan-hud-raised,#28374ad6);border:1px solid var(--mengsan-hud-stroke,#dce8f42e);border-radius:12px;font:inherit;cursor:pointer;touch-action:manipulation;pointer-events:auto; }
|
||||
.mengsan-hud-shuying button:hover,.mengsan-unit-status-shuying:hover { border-color:#dcc18e;background:#ffffff12; }
|
||||
.mengsan-hud-shuying button:focus-visible,.mengsan-unit-status-shuying:focus-visible,.mengsan-hud-dialog-shuying button:focus-visible { outline:2px solid #f4d894;outline-offset:2px; }
|
||||
.mengsan-hud-shuying .mengsan-hud-info-shuying { left:var(--mengsan-profile-info-left);top:var(--mengsan-profile-info-top);bottom:auto;width:100px;display:flex;flex-direction:column;gap:12px; }
|
||||
.mengsan-hud-name-shuying { font:600 18px/1.2 "Microsoft YaHei",sans-serif;letter-spacing:.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap; }
|
||||
.mengsan-hud-hp-shuying,.mengsan-hud-block-shuying { font-size:12px;color:#e2dfd4;font-variant-numeric:tabular-nums;white-space:nowrap; }
|
||||
.mengsan-hud-health-track-shuying { width:100%;height:4px;margin:0 0 2px;background:#ffffff1a;border-radius:4px;overflow:hidden; }
|
||||
.mengsan-hud-health-track-shuying>span { height:100%;background:#9bb78d; }
|
||||
.mengsan-hud-energy-shuying { color:#eacb87;font-size:16px;font-variant-numeric:tabular-nums;white-space:nowrap; }
|
||||
.mengsan-hud-center-shuying { left:40px;right:auto;top:auto;bottom:var(--mengsan-side-dock-bottom);width:var(--mengsan-profile-width);height:var(--mengsan-status-height); }
|
||||
.mengsan-hud-shuying .mengsan-hud-statuses-shuying { display:grid;grid-template-columns:repeat(2,minmax(0,1fr));grid-auto-rows:32px;align-content:end;gap:6px;overflow:hidden;white-space:nowrap;height:100%; }
|
||||
.mengsan-hud-shuying .mengsan-hud-status-shuying { display:flex;align-items:center;gap:4px;max-width:100%;min-width:0;min-height:32px;padding:3px 6px;background:transparent;border-radius:10px; }
|
||||
.mengsan-hud-status-shuying span { overflow:hidden;text-overflow:ellipsis;font-size:12px; }
|
||||
.mengsan-hud-status-shuying b { font-variant-numeric:tabular-nums;font-size:13px; }
|
||||
.mengsan-hud-status-shuying img { width:22px;height:22px;object-fit:contain; }
|
||||
.mengsan-hud-hand-shuying { position:absolute!important;left:var(--mengsan-hud-left)!important;right:var(--mengsan-hud-right)!important;bottom:68px!important;top:auto!important;width:calc(100% - var(--mengsan-hud-left) - var(--mengsan-hud-right))!important;height:calc(var(--mengsan-hud-height,274px) - 126px)!important;z-index:24!important;background:none!important;border:0!important;transform:none!important;pointer-events:none; }
|
||||
.mengsan-hud-hand-shuying .handcards>.card { pointer-events:auto; }
|
||||
.mengsan-hud-mebg-shuying,.mengsan-hud-hand-shuying>.fakeme.avatar { display:none!important; }
|
||||
/* These explicit references cover Decade's nested camp-wrap and hp-wrap as well. */
|
||||
.mengsan-hud-active-shuying .mengsan-hud-replaced-node-shuying { display:none!important; }
|
||||
#arena.mengsan-hud-arena-shuying>.player.mengsan-hud-self-shuying { box-sizing:border-box!important;background:#172131!important;border:1px solid #dce8f44d!important;border-radius:16px!important;box-shadow:0 6px 20px #0005!important;min-width:0!important;min-height:0!important;max-width:none!important;max-height:none!important; }
|
||||
#arena.mengsan-hud-arena-shuying>.player.mengsan-hud-self-shuying { z-index:25!important; }
|
||||
#arena.mengsan-hud-arena-shuying>.player.mengsan-hud-self-shuying::before,#arena.mengsan-hud-arena-shuying>.player.mengsan-hud-self-shuying::after { display:none!important; }
|
||||
#arena.mengsan-hud-arena-shuying>.player.selectable.mengsan-hud-self-shuying { outline:2px solid #e8d198;outline-offset:3px; }
|
||||
#arena.mengsan-hud-arena-shuying>.player.selected.mengsan-hud-self-shuying,#arena.mengsan-hud-arena-shuying>.player.target.mengsan-hud-self-shuying { outline:3px solid #f29b73;outline-offset:3px; }
|
||||
#arena.mengsan-hud-arena-shuying .player>.mengsan-hud-portrait-shuying,#arena.mengsan-hud-arena-shuying .player.mengsan-hud-self-shuying>.dynamic-wrap { position:absolute!important;inset:0!important;width:100%!important;height:100%!important;transform:none!important;clip-path:inset(0 round 14px)!important;-webkit-clip-path:inset(0 round 14px)!important;border-radius:14px!important;background-size:cover!important;background-position:center 15%!important; }
|
||||
.mengsan-hud-portrait-shuying>.action { display:none!important; }
|
||||
.mengsan-hud-self-shuying>.mengsan-energy-shuying,.mengsan-hud-actor-shuying>.mengsan-energy-shuying { display:none!important; }
|
||||
.mengsan-hud-represented-mark-shuying { display:none!important; }
|
||||
/* Container IDs and combo-control differ. Bind to owned classes and keep all callbacks. */
|
||||
/* Only arrange the real native control groups; their theme owns all button paint. */
|
||||
body.mengsan-hud-active-shuying #arena>.mengsan-hud-controls-shuying { height:auto!important;display:flex!important;flex-direction:row!important;flex-wrap:wrap!important;align-items:center!important;align-content:flex-end!important;justify-content:center!important;gap:8px;overflow:visible!important;margin:0!important;padding:0!important;z-index:30!important;pointer-events:none; }
|
||||
body.mengsan-hud-active-shuying #arena>.mengsan-hud-controls-shuying>.control { position:relative!important;left:auto!important;right:auto!important;top:auto!important;bottom:auto!important;transform:none!important;display:flex!important;flex-direction:row!important;flex-wrap:wrap!important;flex:none!important;align-items:center!important;justify-content:center!important;width:auto!important;max-width:100%!important;min-width:0!important;height:auto!important;margin:0!important;overflow:visible!important;pointer-events:none; }
|
||||
body.mengsan-hud-active-shuying #arena>.mengsan-hud-controls-shuying>.control>div { pointer-events:auto; }
|
||||
.mengsan-hud-external-control-shuying { z-index:31!important;overflow:auto!important;box-sizing:border-box; }
|
||||
.mengsan-hud-skin-skills-shuying { height:auto!important;max-width:none!important;display:flex!important;flex-direction:column!important;gap:4px; }
|
||||
.mengsan-hud-skin-skills-shuying>.enable,.mengsan-hud-skin-skills-shuying>.trigger { position:relative!important;display:flex!important;flex-wrap:nowrap!important;flex-direction:column!important;align-items:stretch!important;justify-content:center!important;gap:8px;width:100%!important;height:auto!important;margin:0!important;padding:0!important; }
|
||||
.mengsan-hud-skin-skills-shuying .skillitem { position:relative!important;float:none!important;box-sizing:border-box!important;width:auto!important;min-width:40px!important;max-width:100%!important;height:auto!important;min-height:28px!important;margin:0!important;padding:4px 8px!important;background-size:100% 100%!important;border:0!important;border-radius:10px!important;font:13px/20px "Microsoft YaHei",sans-serif!important;white-space:normal!important; }
|
||||
.mengsan-hud-skin-skills-shuying>.enable>.skillitem { width:100%!important;min-height:34px!important;text-align:center; }
|
||||
.mengsan-hud-skin-skills-shuying .skillitem { -webkit-text-fill-color:currentColor!important;-webkit-text-stroke:0!important;background-clip:border-box!important;-webkit-background-clip:border-box!important; }
|
||||
.mengsan-hud-skin-skills-shuying .skillitem::before,.mengsan-hud-skin-skills-shuying .skillitem-child { display:none!important; }
|
||||
.mengsan-hud-skin-skills-shuying .skillitem:is(.select,.selected) { outline:1px solid #e6cb93;outline-offset:-2px; }
|
||||
.mengsan-hud-skin-skills-shuying>.enable>.skillitem:not(.usable):not(.select),.mengsan-hud-skin-skills-shuying .skillitem.shixiao { opacity:.55; }
|
||||
.mengsan-hud-skin-skills-shuying>.trigger>.skillitem { color:#c2c7c8!important;font-size:12px!important; }
|
||||
.mengsan-hud-active-shuying .mengsan-hand-relics-shuying { position:absolute!important;left:auto!important;right:24px!important;transform:none!important;top:auto!important;bottom:var(--mengsan-relic-dock-bottom)!important;width:128px!important;max-width:128px!important;height:auto;max-height:var(--mengsan-status-height);padding:2px;box-sizing:border-box;display:flex;flex-direction:row;flex-wrap:wrap;justify-content:flex-end;gap:6px;overflow-x:hidden;overflow-y:auto;z-index:22;touch-action:pan-y;background:transparent!important;border:0;border-radius:14px;scrollbar-width:thin;scrollbar-color:#dce8f440 transparent; }
|
||||
.mengsan-hud-active-shuying .mengsan-hand-relics-shuying[hidden] { display:none!important; }
|
||||
.mengsan-hud-active-shuying .mengsan-hand-relics-shuying>button { flex:none;width:32px;min-height:32px;height:32px;border-color:var(--mengsan-hud-stroke);border-radius:10px;background:transparent; }
|
||||
.mengsan-hud-active-shuying .mengsan-hand-relics-shuying img { width:28px;height:28px; }
|
||||
.mengsan-hud-active-shuying .mengsan-hud-hand-shuying .mengsan-hand-scroll-shuying { left:0!important;width:100%!important; }
|
||||
.mengsan-hud-dialog-shuying { position:fixed;inset:0;margin:auto;width:min(620px,calc(100vw - 24px));max-height:82vh;padding:24px;color:#edf2f7;background:#172131ed;border:1px solid #dce8f42e;border-radius:20px;backdrop-filter:blur(10px);overflow:auto;z-index:150;font:15px/1.6 "Microsoft YaHei",sans-serif; }
|
||||
.mengsan-hud-dialog-shuying::backdrop { background:#0009; }
|
||||
.mengsan-hud-dialog-shuying h2 { margin:0 70px 14px 0;font-size:19px; }
|
||||
.mengsan-hud-dialog-close-shuying { position:absolute;right:14px;top:14px;padding:5px 10px;min-height:36px; }
|
||||
.mengsan-hud-detail-row-shuying { border-top:1px solid #cbb58a40;padding:10px 0; }
|
||||
.mengsan-hud-detail-row-shuying p { margin:4px 0; }
|
||||
[data-mengsan-hud-layout="short"] .mengsan-hud-shuying .mengsan-hud-info-shuying { left:108px;width:90px;gap:4px; }
|
||||
[data-mengsan-hud-layout="short"] .mengsan-hud-hp-shuying,[data-mengsan-hud-layout="short"] .mengsan-hud-block-shuying { font-size:11px; }
|
||||
[data-mengsan-hud-layout="short"] .mengsan-hud-name-shuying { font-size:15px; }
|
||||
[data-mengsan-hud-layout="short"] .mengsan-hud-energy-shuying { font-size:14px; }
|
||||
[data-mengsan-hud-layout="short"] .mengsan-hud-hand-shuying { bottom:64px!important;height:calc(var(--mengsan-hud-height) - 122px)!important; }
|
||||
[data-mengsan-hud-layout="short"] .mengsan-hud-center-shuying { top:auto;left:24px; }
|
||||
[data-mengsan-hud-layout="short"] .mengsan-hud-shuying .mengsan-hud-status-shuying { min-height:26px;padding:2px 4px; }
|
||||
[data-mengsan-hud-layout="short"] .mengsan-hud-status-shuying img { width:18px;height:18px; }
|
||||
[data-mengsan-hud-layout="narrow"] .mengsan-hud-shuying .mengsan-hud-info-shuying { left:22px;width:76px;font-size:12px;gap:5px; }
|
||||
[data-mengsan-hud-layout="narrow"] .mengsan-hud-hand-shuying { bottom:68px!important; }
|
||||
[data-mengsan-hud-layout="narrow"] .mengsan-hud-name-shuying { font-size:16px; }
|
||||
[data-mengsan-hud-layout="narrow"] .mengsan-hud-status-shuying span { display:none; }
|
||||
#arena.mengsan-hud-arena-shuying>.mengsan-hud-notification-shuying { width:148px!important;max-width:148px!important;min-height:30px;height:30px!important;transition:none!important;font:600 16px/30px "Microsoft YaHei",sans-serif!important;white-space:nowrap; }
|
||||
#arena.mengsan-hud-arena-shuying>.mengsan-hud-notification-shuying>span { font-size:16px!important; }
|
||||
/* Stack real equipment nodes over the lower portrait, like Decade's player equips. */
|
||||
body.mengsan-hud-active-shuying #arena .mengsan-hud-equips-shuying {
|
||||
position:absolute!important;left:6px!important;right:auto!important;top:auto!important;bottom:6px!important;
|
||||
width:calc(100% - 12px)!important;min-width:0!important;height:auto!important;max-height:min(100px,58%)!important;box-sizing:border-box!important;
|
||||
display:flex!important;flex-direction:column!important;align-items:stretch!important;justify-content:flex-start!important;gap:2px;overflow-x:hidden!important;overflow-y:auto!important;
|
||||
padding:0!important;margin:0!important;transform:none!important;transition:none!important;background:none!important;border:0!important;box-shadow:none!important;
|
||||
pointer-events:auto!important;touch-action:pan-y;scrollbar-width:thin;scrollbar-color:#dce8f440 transparent;z-index:72!important;
|
||||
}
|
||||
body.mengsan-hud-active-shuying #arena .mengsan-hud-equips-shuying:empty { display:none!important; }
|
||||
body.mengsan-hud-active-shuying #arena .mengsan-hud-equip-host-shuying { opacity:1!important;visibility:visible!important;overflow:visible!important;pointer-events:none!important;z-index:26!important; }
|
||||
body.mengsan-hud-active-shuying #arena .mengsan-hud-equip-host-shuying>.equips-back { display:none!important; }
|
||||
body.mengsan-hud-active-shuying #arena .mengsan-hud-equips-shuying>.card {
|
||||
position:relative!important;inset:auto!important;display:flex!important;flex:none!important;align-items:center!important;justify-content:center!important;
|
||||
width:100%!important;min-width:0!important;max-width:100%!important;height:18px!important;margin:0!important;padding:0 2px!important;box-sizing:border-box!important;
|
||||
transform:none!important;transition:none!important;overflow:hidden!important;border:1px solid #5f372d!important;border-radius:3px!important;
|
||||
background:rgba(228,207,166,.88)!important;box-shadow:none!important;color:#181818!important;pointer-events:auto!important;
|
||||
}
|
||||
body.mengsan-hud-active-shuying #arena .mengsan-hud-equips-shuying>.card>.name2 {
|
||||
position:relative!important;inset:auto!important;display:block!important;max-width:100%!important;width:auto!important;height:auto!important;
|
||||
overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important;color:#181818!important;font:11px/16px "Microsoft YaHei",sans-serif!important;
|
||||
text-shadow:none!important;pointer-events:none!important;
|
||||
}
|
||||
body.mengsan-hud-active-shuying #arena .mengsan-hud-equips-shuying>.card>.name2>span { position:static!important;display:inline!important;font:inherit!important; }
|
||||
body.mengsan-hud-active-shuying #arena .mengsan-hud-equips-shuying>.card>:not(.name2) { display:none!important; }
|
||||
body.mengsan-hud-active-shuying #arena .mengsan-hud-equips-shuying>.card.selectable { border-color:#e4c98e!important;box-shadow:inset 0 0 0 1px #e4c98e!important; }
|
||||
body.mengsan-hud-active-shuying #arena .mengsan-hud-equips-shuying>.card.selected { background:#49624b!important;border-color:#e4c98e!important; }
|
||||
|
||||
body.mengsan-hud-active-shuying #arena .mengsan-hud-equips-shuying>.card:is([data-suit="heart"],[data-suit="diamond"])>.name2 { color:#8b0000!important; }
|
||||
body.mengsan-hud-active-shuying #arena .mengsan-hud-equips-shuying::-webkit-scrollbar { width:3px; }
|
||||
body.mengsan-hud-active-shuying #arena .mengsan-hud-equips-shuying::-webkit-scrollbar-thumb { background:#dce8f440;border-radius:3px; }
|
||||
[data-mengsan-hud-layout="short"] .mengsan-hand-relics-shuying { right:16px!important;width:112px!important;max-width:112px!important; }
|
||||
[data-mengsan-hud-layout="narrow"] .mengsan-hand-relics-shuying { right:16px!important;width:96px!important;max-width:96px!important;max-height:36px;flex-wrap:nowrap;justify-content:flex-start;overflow-x:auto;overflow-y:hidden;touch-action:pan-x; }
|
||||
body.mengsan-hud-active-shuying #arena>.mengsan-hud-controls-shuying>.mengsan-hud-native-skills-shuying { flex-direction:column!important;flex-wrap:nowrap!important;align-items:stretch!important;max-width:none!important;overflow:auto!important; }
|
||||
body.mengsan-hud-active-shuying #arena .mengsan-hud-native-skills-shuying>div { width:100%!important;min-width:0!important;box-sizing:border-box!important;padding:8px 10px!important;min-height:40px!important;font:14px/20px "Microsoft YaHei",sans-serif!important;white-space:normal!important; }
|
||||
[data-mengsan-hud-layout="narrow"] .mengsan-hud-center-shuying { left:22px; }
|
||||
[data-mengsan-hud-layout="short"] .mengsan-hud-shuying .mengsan-hud-statuses-shuying { height:100%; }
|
||||
[data-mengsan-hud-layout="narrow"] .mengsan-hud-shuying .mengsan-hud-statuses-shuying { grid-template-columns:1fr; }
|
||||
.mengsan-hud-prompt-shuying { box-sizing:border-box!important;height:auto!important;min-height:0!important;overflow:auto!important;padding:2px 6px!important;background:none!important;border:0!important;box-shadow:none!important;text-align:center!important;z-index:32!important;color:#f6edcb!important;text-shadow:0 1px 3px #000,0 0 6px #000!important; }
|
||||
body.mengsan-hud-active-shuying .mengsan-hud-card-intro-shuying { box-sizing:border-box!important;z-index:90!important;height:auto!important;min-height:0!important;overflow:auto!important;border-radius:14px!important;background:#202020f5!important;box-shadow:0 8px 24px #0007!important;color:#f4f4f4!important;transition:none!important; }
|
||||
body.mengsan-hud-active-shuying #window>.mengsan-hud-card-intro-shuying { z-index:90!important; }
|
||||
body.mengsan-hud-active-shuying #window>.mengsan-hud-intro-layer-shuying { z-index:89!important; }
|
||||
.mengsan-hud-card-intro-shuying>.content-container,.mengsan-hud-prompt-shuying>.content-container { position:relative!important;inset:auto!important;height:auto!important;max-height:none!important;overflow:visible!important; }
|
||||
.mengsan-hud-card-intro-shuying>.content-container>.content,.mengsan-hud-prompt-shuying>.content-container>.content { position:relative!important;inset:auto!important;height:auto!important; }
|
||||
.mengsan-hud-card-intro-shuying .content>div,.mengsan-hud-prompt-shuying .content>div { position:relative!important; }
|
||||
body.mengsan-hud-active-shuying .mengsan-hud-intro-layer-shuying { z-index:89!important;transition:none!important; }
|
||||
.mengsan-hud-frozen-shuying .mengsan-hud-hand-shuying,.mengsan-hud-frozen-shuying .mengsan-hud-self-shuying,.mengsan-hud-frozen-shuying .mengsan-hud-shuying { pointer-events:none!important; }
|
||||
.mengsan-hud-frozen-shuying .mengsan-hud-hand-shuying *,.mengsan-hud-frozen-shuying .mengsan-hud-self-shuying *,.mengsan-hud-frozen-shuying .mengsan-hud-shuying * { pointer-events:none!important; }
|
||||
.mengsan-hud-frozen-shuying .mengsan-hud-controls-shuying,.mengsan-hud-frozen-shuying .mengsan-hud-external-control-shuying,.mengsan-hud-frozen-shuying .hand-tip,.mengsan-hud-frozen-shuying .hand-tip-info { visibility:hidden!important; }
|
||||
Reference in New Issue
Block a user