Merge remote-tracking branch 'origin/Main' into test-gay

This commit is contained in:
CAtlas0784 committed 2026-10-31 22:04:22 +07:00
commit a3b722e132
13 files changed
+1464 -57

No files matched your search

+68 -32
View File
@@ -1,8 +1,10 @@
import { COMMANDS, usage, inspectCommand, completeCommand } from './command-assistant.mjs';
import { AVATAR_METADATA, getAvatarInfo } from './avatars-data.js';
import { LIGHTCONE_METADATA, RELIC_SET_METADATA, getLightconeInfo, getRelicSetInfo } from './equipment-data.js';
import { initializeItemCatalog } from './item-catalog.js?v=2';
import { initializeUnstuck } from './unstuck.js?v=1';
import { PRYDWEN_BUILD_GUIDES } from './build-guides-data.js';
import { SUPPORTED_LANGUAGES, TRANSLATIONS, getCurrentLanguage, setLanguage, t, translateElement, translatePath } from './i18n.js?v=2.12';
import { SUPPORTED_LANGUAGES, TRANSLATIONS, getCurrentLanguage, setLanguage, t, translateElement, translatePath } from './i18n.js?v=2.13';
import {
getAvatarIconUrl,
getLightconeArtUrl,
@@ -963,6 +965,12 @@ function applyTranslations() {
if (val) elem.alt = val;
});
document.querySelectorAll('[data-i18n-aria-label]').forEach(elem => {
const key = elem.getAttribute('data-i18n-aria-label');
const val = t(key);
if (val) elem.setAttribute('aria-label', val);
});
// Update dynamic filter dropdown options
updateFilterDropdownTranslations();
@@ -1535,6 +1543,17 @@ function hasAvatarBattleStatsData(avatars) {
});
}
function hasAvatarDamageBreakdownData(avatar) {
const damageTypes = avatar?.attack_type_damage;
return Number(avatar?.total_break_damage) > 0
|| (damageTypes && Object.keys(damageTypes).length > 0);
}
function hasAvatarMaxHitData(avatar) {
const maxDamage = avatar?.attack_type_max_damage;
return Boolean(maxDamage && Object.values(maxDamage).some(value => Number(value) > 0));
}
function getAvatarDamagePercent(avatar, teamDamage, available) {
if (!available) return null;
const reportedPercent = Number(avatar?.damage_percent);
@@ -6988,7 +7007,10 @@ function renderMonsterChallengeMode(mode) {
return;
}
const selectedGroup = groups.find(group => String(group.id) === String(state.monsterSettings.groupId)) || groups[0];
const preferredGroupId = mode === 'aa'
? (state.monsterSettings.aaRoundSelection?.peak_group_id ?? groups[0].id)
: state.monsterSettings.groupId;
const selectedGroup = groups.find(group => String(group.id) === String(preferredGroupId)) || groups[0];
state.monsterSettings.groupId = String(selectedGroup.id);
const selectedStage = selectedGroup.stages.find(stage => String(stage.id) === String(state.monsterSettings.stageId)) || selectedGroup.stages[0];
state.monsterSettings.stageId = String(selectedStage.id);
@@ -9230,30 +9252,30 @@ function renderEmptyDamageAnalyst() {
function orderBattleReportAvatars(avatars) {
if (!Array.isArray(avatars) || avatars.length < 2) return avatars || [];
if (analystState.currentReport?.is_currency_wars || avatars.length > 4) return avatars;
if ((typeof analystState !== 'undefined' && analystState.currentReport?.is_currency_wars) || avatars.length > 4) return avatars;
const lineup = state.statusData?.lineups;
if (!lineup) return avatars;
const lineupSlots = Array.isArray(lineup)
? lineup.map((avatarId, slot) => [slot, avatarId])
: Object.entries(lineup).sort(([left], [right]) => Number(left) - Number(right));
const lineupIds = lineupSlots
.map(([, avatarId]) => Number(avatarId))
.filter(avatarId => Number.isSafeInteger(avatarId) && avatarId > 0);
const reportById = new Map();
for (const avatar of avatars) {
const avatarId = Number(avatar?.avatar_id);
if (!Number.isSafeInteger(avatarId) || avatarId <= 0 || reportById.has(avatarId)) {
return avatars;
}
reportById.set(avatarId, avatar);
// Battle slots remain stable when the overworld team changes or the native
// statistics dictionary enumerates characters in a different order.
const slots = avatars.map(avatar => avatar?.slot);
if (slots.every(slot => Number.isSafeInteger(slot) && slot >= 0)
&& new Set(slots).size === avatars.length) {
return [...avatars].sort((left, right) => left.slot - right.slot);
}
const orderedIds = [...new Set(lineupIds)].filter(avatarId => reportById.has(avatarId));
if (orderedIds.length !== avatars.length) return avatars;
return orderedIds.map(avatarId => reportById.get(avatarId));
// A historical report without battle slots must keep its recorded order.
return avatars;
}
function damageBreakdownForAvatar(avatar) {
const damage = avatar.attack_type_damage || {};
const normal = Number(damage.Normal) || 0;
const skill = Number(damage.BPSkill ?? damage.Skill) || 0;
const ultimate = Number(damage.Ultra ?? damage.Ultimate) || 0;
const breakDamage = Number(avatar.total_break_damage || damage.BreakDamage) || 0;
// Aggregate totals can include damage without an attack-type entry. Keep it
// visible without counting named subcategories a second time.
const other = Math.max(0, (Number(avatar.total_damage) || 0) - normal - skill - ultimate - breakDamage);
return { normal, skill, ultimate, breakDamage, other };
}
function renderDamageAnalyst() {
@@ -9364,6 +9386,8 @@ function renderDamageAnalyst() {
const color = colors[idx % colors.length];
const info = getAvatarInfo(a.avatar_id);
const pct = getAvatarDamagePercent(a, teamDmg, hasAvatarDamage);
const hasDamageBreakdown = hasAvatarDamageBreakdownData(a);
const hasMaxHit = hasAvatarMaxHitData(a);
let maxHit = 0;
if (a.attack_type_max_damage) {
@@ -9372,10 +9396,7 @@ function renderDamageAnalyst() {
}
}
const dNormal = a.attack_type_damage?.Normal || 0;
const dSkill = a.attack_type_damage?.BPSkill || a.attack_type_damage?.Skill || 0;
const dUlt = a.attack_type_damage?.Ultra || a.attack_type_damage?.Ultimate || 0;
const dBreak = a.total_break_damage || a.attack_type_damage?.BreakDamage || 0;
const { normal: dNormal, skill: dSkill, ultimate: dUlt, breakDamage: dBreak, other: dOther } = damageBreakdownForAvatar(a);
const dFollowup = (a.attack_type_damage?.Insert || 0) + (a.attack_type_damage?.Pursued || 0);
const dDot = a.attack_type_damage?.DOT || 0;
@@ -9416,10 +9437,11 @@ function renderDamageAnalyst() {
<div>
<div style="font-size: 0.725rem; font-weight: 700; color: var(--text-muted); text-transform: uppercase;">${t('analystTotalDamage')}</div>
<div style="font-size: 1.5rem; font-weight: 900; color: #fff;">${hasAvatarDamage ? formatNumber(a.total_damage) : '—'}</div>
${Number(a.memosprite_damage) > 0 ? `<div style="font-size: 0.7rem; color: var(--text-muted);">Memosprite: ${formatNumber(a.memosprite_damage)} (included)</div>` : ''}
</div>
<div style="text-align: right;">
<div style="font-size: 0.725rem; color: var(--text-muted);">${t('analystMaxHit')}</div>
<div style="font-size: 0.95rem; font-weight: 800; color: #f87171;">${hasAvatarDamage ? formatNumber(maxHit) : '—'}</div>
<div style="font-size: 0.95rem; font-weight: 800; color: #f87171;">${hasMaxHit ? formatNumber(maxHit) : '—'}</div>
</div>
</div>
@@ -9433,19 +9455,23 @@ function renderDamageAnalyst() {
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 0.4rem; margin-bottom: 1rem;">
<div style="padding: 0.4rem 0.6rem; border-radius: 6px; background: rgba(255,255,255,0.02); border: 1px solid rgba(255,255,255,0.04); display: flex; justify-content: space-between; font-size: 0.775rem;">
<span style="color: var(--text-muted);">${t('analystDmgNormal')}</span>
<span style="font-weight: 700; color: var(--text-main);">${hasAvatarDamage ? formatNumber(dNormal) : '—'}</span>
<span style="font-weight: 700; color: var(--text-main);">${hasDamageBreakdown ? formatNumber(dNormal) : '—'}</span>
</div>
<div style="padding: 0.4rem 0.6rem; border-radius: 6px; background: rgba(255,255,255,0.02); border: 1px solid rgba(255,255,255,0.04); display: flex; justify-content: space-between; font-size: 0.775rem;">
<span style="color: var(--text-muted);">${t('analystDmgSkill')}</span>
<span style="font-weight: 700; color: #38bdf8;">${hasAvatarDamage ? formatNumber(dSkill) : '—'}</span>
<span style="font-weight: 700; color: #38bdf8;">${hasDamageBreakdown ? formatNumber(dSkill) : '—'}</span>
</div>
<div style="padding: 0.4rem 0.6rem; border-radius: 6px; background: rgba(255,255,255,0.02); border: 1px solid rgba(255,255,255,0.04); display: flex; justify-content: space-between; font-size: 0.775rem;">
<span style="color: var(--text-muted);">${t('analystDmgUlt')}</span>
<span style="font-weight: 700; color: #c084fc;">${hasAvatarDamage ? formatNumber(dUlt) : '—'}</span>
<span style="font-weight: 700; color: #c084fc;">${hasDamageBreakdown ? formatNumber(dUlt) : '—'}</span>
</div>
<div style="padding: 0.4rem 0.6rem; border-radius: 6px; background: rgba(255,255,255,0.02); border: 1px solid rgba(255,255,255,0.04); display: flex; justify-content: space-between; font-size: 0.775rem;">
<span style="color: var(--text-muted);">${t('analystDmgBreak')}</span>
<span style="font-weight: 700; color: #fbbf24;">${hasAvatarDamage ? formatNumber(dBreak) : '—'}</span>
<span style="font-weight: 700; color: #fbbf24;">${hasDamageBreakdown ? formatNumber(dBreak) : '—'}</span>
</div>
<div style="padding: 0.4rem 0.6rem; border-radius: 6px; background: rgba(255,255,255,0.02); border: 1px solid rgba(255,255,255,0.04); display: flex; justify-content: space-between; font-size: 0.775rem;">
<span style="color: var(--text-muted);">Other</span>
<span style="font-weight: 700; color: #f472b6;">${hasAvatarDamage ? formatNumber(dOther) : '—'}</span>
</div>
${dFollowup > 0 ? `
<div style="padding: 0.4rem 0.6rem; border-radius: 6px; background: rgba(255,255,255,0.02); border: 1px solid rgba(255,255,255,0.04); display: flex; justify-content: space-between; font-size: 0.775rem;">
@@ -9459,6 +9485,14 @@ function renderDamageAnalyst() {
<span style="font-weight: 700; color: #a78bfa;">${formatNumber(dDot)}</span>
</div>
` : ''}
${Object.entries(a.attack_type_damage || {})
.filter(([type, damage]) => Number(damage) > 0 && !['Normal', 'BPSkill', 'Skill', 'Ultra', 'Ultimate', 'BreakDamage', 'Insert', 'Pursued', 'DOT'].includes(type))
.map(([type, damage]) => `
<div style="padding: 0.4rem 0.6rem; border-radius: 6px; background: rgba(255,255,255,0.02); border: 1px solid rgba(255,255,255,0.04); display: flex; justify-content: space-between; gap: 0.4rem; font-size: 0.775rem;">
<span style="color: var(--text-muted);">${escapeHtml(type.replace(/([a-z])([A-Z])/g, '$1 $2'))}</span>
<span style="font-weight: 700; color: #f472b6;">${formatNumber(Number(damage))}</span>
</div>
`).join('')}
</div>
<div style="display: flex; gap: 0.75rem; flex-wrap: wrap; padding-top: 0.65rem; border-top: 1px solid rgba(255,255,255,0.06); font-size: 0.75rem; color: var(--text-muted);">
@@ -9752,6 +9786,8 @@ async function init() {
await loadCachedStorageData();
setupServerModes();
setupLanguageSelector();
initializeItemCatalog();
initializeUnstuck({ apiCall, isConnected: () => state.isConnected, showToast });
setupTabs();
setupModalSubtabs();
setupEvents();