diff --git a/README.md b/README.md index a6a2187..ffe2977 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,8 @@ GaC-Srtool สื่อสารกับเซิร์ฟเวอร์ GaC-h ### บันทึกตัวละครและ Traces +Damage Analyst เรียงการ์ดตาม `report.avatars[].slot` จาก Battle จริง ไม่เปลี่ยนตามทีมหน้าโลก รายงานเก่าที่ไม่มี slot คงลำดับเดิม และแสดงประเภทดาเมจเพิ่มเติมที่ API ส่งมา เช่น Elation Damage / True Damage ทดสอบลำดับด้วย `node --test tests/damage-order.test.cjs` ข้อมูลสดต้องใช้ SDK และ `damage_analyst_live.lua` ฉบับที่อ่าน StatisticsComponent ของ Battle ปัจจุบัน + การบันทึกจากหน้ารายละเอียดส่งข้อมูลตัวละครทั้งชุดผ่าน `/api/freesr` ในคำขอเดียว รวมถึง `data.skills` และ `data.skills_by_anchor_type` สำหรับ Traces เพื่อไม่ให้ server sync ข้อมูลระดับ/Eidolon ระหว่างทางก่อนข้อมูล Trace จะถูกบันทึก `saveAvatarDetails(closeModal = true)` ยังรองรับการเรียกจากปุ่ม build ที่ไม่ต้องปิดหน้าต่าง ถ้า server ตอบผิดพลาดจะแสดงข้อความบันทึกไม่สำเร็จแทนการแจ้งว่าสำเร็จ ### ข้อมูล Build และแหล่งที่มา diff --git a/js/app.js b/js/app.js index 842baa5..1169cb6 100644 --- a/js/app.js +++ b/js/app.js @@ -1521,6 +1521,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); @@ -8368,28 +8379,16 @@ function renderEmptyDamageAnalyst() { function orderBattleReportAvatars(avatars) { if (!Array.isArray(avatars) || avatars.length < 2) 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 renderDamageAnalyst() { @@ -8497,6 +8496,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) { @@ -8552,7 +8553,7 @@ function renderDamageAnalyst() {