forked from xziino/ff14-mitigator
Move boss debuffs to AoE event header, update CLAUDE.md
Debuffs (Reprisal, Feint, Addle, etc.) now appear once next to the ability name and REF label instead of repeating under each player card. Flex layout on .aoe-ability matches the REF row spacing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
14d1afc7d9
commit
e85e341b33
94
CLAUDE.md
94
CLAUDE.md
@ -5,8 +5,8 @@ PHP/HTML/JS-Tool zum Analysieren von FFXIV-Raidlogs via FFLogs OAuth2 PKCE + Gra
|
|||||||
Kein Framework, kein Composer, kein npm — Plain PHP für Shared Hosting.
|
Kein Framework, kein Composer, kein npm — Plain PHP für Shared Hosting.
|
||||||
|
|
||||||
Zwei Tabs:
|
Zwei Tabs:
|
||||||
- **Report-Tab**: Report-Code eingeben, Fight auswählen, Raw-JSON-Ausgabe
|
- **Report-Tab**: Report-Code eingeben, Fight auswählen → Fight-JSON-Ausgabe + Event Explorer
|
||||||
- **Analyse-Tab**: Spielerübersicht + AoE-Timeline mit Mitigation-Tracking
|
- **Analyse-Tab**: Spielerübersicht + AoE-Timeline mit Mitigation-Tracking, Pull-Vergleich, Phase-Filter
|
||||||
|
|
||||||
## Architektur & Konventionen
|
## Architektur & Konventionen
|
||||||
|
|
||||||
@ -22,11 +22,12 @@ Zwei Tabs:
|
|||||||
### Geteilter JS-State
|
### Geteilter JS-State
|
||||||
`window.App` in `app.js` hält den gemeinsamen State für alle Tabs:
|
`window.App` in `app.js` hält den gemeinsamen State für alle Tabs:
|
||||||
```js
|
```js
|
||||||
window.App = { reportCode, fightId, fightStart, fightEnd }
|
window.App = { reportCode, fightId, fightStart, fightEnd, phases: [], fights: [] }
|
||||||
```
|
```
|
||||||
`window.analysisTab` (definiert in `analysis.js`) stellt Hooks bereit:
|
`window.analysisTab` (definiert in `analysis.js`) stellt Hooks bereit:
|
||||||
- `onFightSelected()` — wird von `app.js` aufgerufen wenn ein Fight gewählt wird
|
- `onFightSelected()` — wird von `app.js` aufgerufen wenn ein Fight gewählt wird
|
||||||
- `onTabOpen()` — wird von `tabs.js` aufgerufen wenn der Analyse-Tab geöffnet wird
|
- `onTabOpen()` — wird von `tabs.js` aufgerufen wenn der Analyse-Tab geöffnet wird
|
||||||
|
- `onFightsLoaded(fights)` — wird von `app.js` aufgerufen nach Report-Load (befüllt Vergleichs-Dropdown)
|
||||||
- `reset()` — wird von `app.js` aufgerufen wenn ein neuer Report geladen wird
|
- `reset()` — wird von `app.js` aufgerufen wenn ein neuer Report geladen wird
|
||||||
|
|
||||||
## Dateistruktur
|
## Dateistruktur
|
||||||
@ -37,26 +38,29 @@ templates/
|
|||||||
page.php — HTML-Skeleton (head, body), routet zu login oder app
|
page.php — HTML-Skeleton (head, body), routet zu login oder app
|
||||||
login.php — Login-Overlay (nicht authentifiziert / Token abgelaufen)
|
login.php — Login-Overlay (nicht authentifiziert / Token abgelaufen)
|
||||||
topbar.php — Topbar mit Logo + Tab-Navigation + Token-Ablaufzeit
|
topbar.php — Topbar mit Logo + Tab-Navigation + Token-Ablaufzeit
|
||||||
tab-report.php — Report-Tab: includes report-form, fight-select, output-card
|
tab-report.php — Report-Tab: includes report-form, fight-select, event-explorer, output-card
|
||||||
tab-analysis.php — Analyse-Tab: Spieler-Grid + AoE-Timeline HTML
|
tab-analysis.php — Analyse-Tab: Spieler-Grid + Pull-Vergleich + AoE-Timeline HTML
|
||||||
report-form.php — Report-Code-Eingabe Card
|
report-form.php — Report-Code-Eingabe Card
|
||||||
fight-select.php — Fight-Auswahl Dropdown Card
|
fight-select.php — Fight-Auswahl Dropdown Card
|
||||||
|
event-explorer.php — Event Explorer Card (Ability/DataType/EventType/Spieler-Filter)
|
||||||
output-card.php — Terminal-Ausgabe Card + Initial-Hint
|
output-card.php — Terminal-Ausgabe Card + Initial-Hint
|
||||||
css/
|
css/
|
||||||
base.css — CSS-Variablen, Reset, Basis-Styles, Feedback-Klassen
|
base.css — CSS-Variablen, Reset, Basis-Styles, Feedback-Klassen
|
||||||
layout.css — App-Shell, Topbar, Tabs, Login-Overlay, Form-Helpers
|
layout.css — App-Shell, Topbar, Tabs, Login-Overlay, Form-Helpers
|
||||||
components.css — Cards, Inputs, Buttons, Badges, Terminal
|
components.css — Cards, Inputs, Buttons, Badges, Terminal
|
||||||
analysis.css — Spieler-Grid, AoE-Timeline, Mitigation-Icons
|
analysis.css — Spieler-Grid, AoE-Timeline, Mitigation-Icons, HP-Bar, Ref-Row
|
||||||
js/
|
js/
|
||||||
app.js — Formular, Fight-Dropdown, Fetch, window.App State
|
app.js — Formular, Fight-Dropdown, Fetch, window.App State, Event Explorer
|
||||||
tabs.js — Tab-Switching, ruft window.analysisTab.onTabOpen() auf
|
tabs.js — Tab-Switching, ruft window.analysisTab.onTabOpen() auf
|
||||||
analysis.js — Analyse-Tab: Daten laden, Spieler rendern, Timeline rendern
|
analysis.js — Analyse-Tab: Daten laden, Spieler rendern, Timeline rendern, Pull-Vergleich
|
||||||
auth/
|
auth/
|
||||||
start.php — PKCE generieren, Session speichern, Redirect zu FFLogs
|
start.php — PKCE generieren, Session speichern, Redirect zu FFLogs
|
||||||
callback.php — Code gegen Token tauschen, Token in Session speichern
|
callback.php — Code gegen Token tauschen, Token in Session speichern
|
||||||
api/
|
api/
|
||||||
fight.php — POST-Endpunkt: Fight-Liste via GraphQL → JSON
|
fight.php — POST-Endpunkt: Fight-Liste via GraphQL → JSON
|
||||||
analysis.php — POST-Endpunkt: Spieler + AoE-Events + Mitigations → JSON
|
analysis.php — POST-Endpunkt: Spieler + AoE-Events + Mitigations → JSON
|
||||||
|
abilities.php — POST-Endpunkt: Ability- + Spielerliste für Event Explorer Dropdowns
|
||||||
|
debug-events.php — POST-Endpunkt: Raw Events für Event Explorer (mit Filterung)
|
||||||
assets/
|
assets/
|
||||||
icons/mitigation/ — Lokal gespeicherte Ability-Icons (PNG, von XIVAPI)
|
icons/mitigation/ — Lokal gespeicherte Ability-Icons (PNG, von XIVAPI)
|
||||||
debug/
|
debug/
|
||||||
@ -75,11 +79,11 @@ CSS-Variablen in `css/base.css`:
|
|||||||
## Analyse-Tab — Konzepte & Entscheidungen
|
## Analyse-Tab — Konzepte & Entscheidungen
|
||||||
|
|
||||||
### AoE-Erkennung
|
### AoE-Erkennung
|
||||||
- Nur `calculateddamage` Events (post-Mitigation Snapshot) — **nicht** `damage` (Application), da sonst doppelte Events
|
- Verwendet `damage` Events mit `includeResources: true` (enthält `buffs`-Feld für Mitigations)
|
||||||
- Gruppierung: 300ms-Zeitfenster × `abilityGameID` → Bucket
|
- Tick-Schaden (`ev['tick'] = true`) und `abilityGameID ≤ 7` (Auto-Attacks) werden gefiltert
|
||||||
- AoE = Bucket mit ≥ 3 unterschiedlichen `targetID`s
|
- **Proximity Clustering** statt fixer Zeitfenster: Events werden pro `abilityGameID` gesammelt, dann in Cluster eingeteilt — ein neuer Cluster beginnt wenn der Abstand zum ersten Event im aktuellen Cluster `> CLUSTER_WINDOW_MS` (1000ms) beträgt
|
||||||
- Auto-Attacks und Fähigkeiten mit `abilityGameID ≤ 7` werden gefiltert
|
- AoE = Cluster mit ≥ 3 unterschiedlichen `targetID`s
|
||||||
- Tick-Schaden (`ev['tick'] = true`) wird ignoriert
|
- Pro Target werden `amount`, `absorbed`, `overkill`, `hp` (nach Treffer), `maxHp` und `buffs` gespeichert; bei mehreren Hits desselben Targets pro Cluster werden `amount`/`absorbed`/`overkill` summiert
|
||||||
|
|
||||||
### Spielernamen statt IDs
|
### Spielernamen statt IDs
|
||||||
- `masterData.abilities` (gameID → Name) wird zusammen mit `playerDetails` in einem einzigen Query abgerufen
|
- `masterData.abilities` (gameID → Name) wird zusammen mit `playerDetails` in einem einzigen Query abgerufen
|
||||||
@ -106,11 +110,12 @@ Getrackte party-wide Buffs + Boss-Debuffs (definiert in `MITIGATION_ABILITIES` i
|
|||||||
| Troubadour | 15% | buff |
|
| Troubadour | 15% | buff |
|
||||||
| Tactician | 15% | buff |
|
| Tactician | 15% | buff |
|
||||||
| Shield Samba | 15% | buff |
|
| Shield Samba | 15% | buff |
|
||||||
|
| Magick Barrier | 10% | buff |
|
||||||
| Reprisal | 10% | debuff |
|
| Reprisal | 10% | debuff |
|
||||||
| Feint | 10% | debuff |
|
| Feint | 10% | debuff |
|
||||||
| Addle | 10% | debuff |
|
| Addle | 10% | debuff |
|
||||||
|
|
||||||
**Fenster-Tracking:** `applybuff`/`applydebuff` öffnet Fenster (nur erstes pro `abilityId_sourceId`-Key, da party-wide Buffs einmal pro Partymitglied feuern). `removebuff`/`removedebuff` schließt das Fenster. Noch offene Fenster am Fight-Ende werden mit `endTime` geschlossen.
|
**Implementierung via `buffs`-Feld:** Mitigations werden nicht über separate `applybuff`/`removebuff`-Events getrackt, sondern direkt aus dem `buffs`-Feld jedes `damage`-Events gelesen. Das Feld enthält einen `.`-separierten String von `abilityGameID`s der aktiven Buffs. `resolveMitigations()` mappt diese IDs auf `MITIGATION_ABILITIES`-Einträge via `$mitigIdMap` (gameID → Meta). Doppelte Namen werden dedupliziert.
|
||||||
|
|
||||||
### Mitigation-Icons
|
### Mitigation-Icons
|
||||||
- Icons lokal gespeichert in `assets/icons/mitigation/` als PNG
|
- Icons lokal gespeichert in `assets/icons/mitigation/` als PNG
|
||||||
@ -120,25 +125,52 @@ Getrackte party-wide Buffs + Boss-Debuffs (definiert in `MITIGATION_ABILITIES` i
|
|||||||
- Mapping in `analysis.js`: `MITIG_ICONS` Objekt (Ability-Name → lokaler Pfad)
|
- Mapping in `analysis.js`: `MITIG_ICONS` Objekt (Ability-Name → lokaler Pfad)
|
||||||
- Darstellung: 16×16px Icon unter jedem Spieler-Target, kein Text, `title`-Tooltip mit Name + DR% + Caster
|
- Darstellung: 16×16px Icon unter jedem Spieler-Target, kein Text, `title`-Tooltip mit Name + DR% + Caster
|
||||||
|
|
||||||
## Geplante Features
|
## Implementierte Features (Übersicht neuerer Commits)
|
||||||
|
|
||||||
### HP-Balken pro Spieler (nächstes Feature)
|
### HP-Balken pro Spieler
|
||||||
Für jeden Spieler in der AoE-Timeline einen 3-Segment-Balken anzeigen, der den HP-Stand im Kontext des Treffers zeigt:
|
2-Segment-Balken direkt unter Name+Schaden in der Spieler-Box:
|
||||||
|
- **Grün** (links): HP nach dem Treffer — Farbton dynamisch: >50% grün, 25–50% amber, <25% rot
|
||||||
|
- **Rot** (Mitte): erlittener Schaden (als % von MaxHP)
|
||||||
|
- Nur gerendert wenn `t.maxHp > 0`; Daten kommen aus `targetResources.hitPoints` / `maxHitPoints` via `includeResources: true`
|
||||||
|
|
||||||
```
|
### Death-Highlight + Absorbed/Overkill
|
||||||
[████████████▓▓▓░░░░░░░░]
|
- Spieler-Box bekommt Klasse `.aoe-target--dead` wenn `hp === 0 && maxHp > 0`
|
||||||
^verbleibend ^Schaden ^vorher schon fehlend
|
- Overkill-Betrag wird links neben dem Job-Badge angezeigt (`.aoe-target-overkill`)
|
||||||
```
|
- Absorbed-Betrag erscheint als gedimmter Zusatz neben dem Schaden (`.aoe-target-absorbed`)
|
||||||
|
|
||||||
- **Grün** (links): HP nach dem Treffer (als % von MaxHP)
|
### Phase-Filter
|
||||||
- **Rot/Orange** (Mitte): erlittener Schaden (als % von MaxHP)
|
- Liest `phaseTransitions` aus dem gewählten Fight-Objekt (`window.App.phases`)
|
||||||
- **Dunkelgrau** (rechts): HP die bereits vor dem Treffer fehlten
|
- Dropdown `#phase-select` erscheint nur wenn Phasen vorhanden sind
|
||||||
- Grünton dynamisch: >50% grün, 25–50% gelb/amber, <25% rot
|
- Option "Ganzer Fight" (id=0) ist immer dabei; individuelle Phasen ab id=1
|
||||||
- Balken sitzt direkt unter Name+Schaden-Zeile in der Spieler-Box
|
- `phaseFilter = { startTime, endTime }` filtert die Timeline in `renderTimeline()`
|
||||||
|
|
||||||
**Datenbedarf:** FFLogs `calculateddamage` Events enthalten `hitPoints` (HP vor dem Treffer) und `maxHitPoints`. Diese müssen in `api/analysis.php` pro Target mitgegeben werden.
|
### Spieler-Sortierung
|
||||||
|
Konsistentes Healer → DPS → Tank-Ordering überall: im Spieler-Grid, in jedem AoE-Event, und in der Referenz-Zeile. Innerhalb gleicher Rolle alphabetisch nach Name.
|
||||||
|
|
||||||
**Backend-Änderung:** In `$buckets[$key]['targets'][$tgtId]` zusätzlich `hp` und `maxHp` aus dem letzten Event speichern und in der Response durchreichen.
|
### Pull-Vergleich (innerhalb desselben Reports)
|
||||||
|
- Dropdown `#ref-fight-select` wird nach Report-Load mit allen Fights befüllt (via `onFightsLoaded`)
|
||||||
|
- Bei Auswahl: separater `api/analysis.php`-Call für den Ref-Fight → `refEvents[]`
|
||||||
|
- In `renderTimeline()`: per `abilityName` und Occurrence-Index gematchter Ref-Event wird als `REF`-Zeile unterhalb der aktuellen Targets gerendert
|
||||||
|
- Fehlende Mitigations (vorhanden im Ref, nicht im aktuellen Pull) werden als ausgegrautem Icon mit Klasse `.aoe-buff-missing` gezeigt
|
||||||
|
- Schaden-Delta pro Spieler: grün wenn besser (`aoe-delta-better`), rot wenn schlechter (`aoe-delta-worse`)
|
||||||
|
- Gesamt-Delta in der REF-Headerzeile
|
||||||
|
|
||||||
|
### Cross-Report-Vergleich
|
||||||
|
- Button "+ Anderer Report" (`#ref-ext-toggle`) öffnet Panel mit Eingabefeld + Laden-Button
|
||||||
|
- `api/fight.php` wird mit dem externen Report-Code aufgerufen → Fight-Dropdown befüllt
|
||||||
|
- Auswahl eines externen Fights → `api/analysis.php`-Call mit externem Code → `refEvents[]`
|
||||||
|
- Same-Report- und Cross-Report-Selektion schließen sich gegenseitig aus (jeweils Reset des anderen Dropdowns)
|
||||||
|
|
||||||
|
### Event Explorer (im Report-Tab)
|
||||||
|
Erscheint nach Fight-Auswahl als Card über dem Output-Terminal. Filteroptionen:
|
||||||
|
- **Ability**: Dropdown mit allen NPC-Fähigkeiten des Fights (via `api/abilities.php`)
|
||||||
|
- **DataType**: DamageTaken / DamageDone / Healing / Casts / Buffs / Deaths
|
||||||
|
- **Event-Typ (raw)**: damage, calculateddamage, applybuff, removebuff, etc.
|
||||||
|
- **Spieler**: Dropdown mit allen Spielern des Fights (via `api/abilities.php`)
|
||||||
|
- **Limit**: 1–500 Events
|
||||||
|
- **Von/Bis**: Zeitoffsets in Sekunden vom Fight-Start
|
||||||
|
- Output landet im gleichen `#output`-Terminal wie der Fight-JSON
|
||||||
|
- Backend `api/debug-events.php`: filtert Events nach Typ, Ability-ID und Spieler (Source oder Target); gibt Metadaten + gefilterte Events zurück
|
||||||
|
|
||||||
## Konfiguration
|
## Konfiguration
|
||||||
- `config.php`: `CLIENT_ID`, `REDIRECT_URI`, `DEV_MODE` anpassen
|
- `config.php`: `CLIENT_ID`, `REDIRECT_URI`, `DEV_MODE` anpassen
|
||||||
@ -151,7 +183,8 @@ Für jeden Spieler in der AoE-Timeline einen 3-Segment-Balken anzeigen, der den
|
|||||||
- GraphQL Endpoint (user-scoped): `https://www.fflogs.com/api/v2/user`
|
- GraphQL Endpoint (user-scoped): `https://www.fflogs.com/api/v2/user`
|
||||||
- Token Endpoint: `https://www.fflogs.com/oauth/token`
|
- Token Endpoint: `https://www.fflogs.com/oauth/token`
|
||||||
- Kein Refresh Token für öffentliche Clients — abgelaufene Sessions starten PKCE neu
|
- Kein Refresh Token für öffentliche Clients — abgelaufene Sessions starten PKCE neu
|
||||||
- Event-Typen: `calculateddamage` (Snapshot nach Mitigation, den wir nutzen) vs. `damage` (Application, ignorieren)
|
- Event-Typen: `damage` (post-Mitigation Snapshot, enthält `buffs`-Feld — den wir nutzen) vs. `calculateddamage` (Application-Zeitpunkt, ignorieren)
|
||||||
|
- `includeResources: true` im `events()`-Query liefert `targetResources.hitPoints` / `maxHitPoints` pro Event
|
||||||
|
|
||||||
## XIVAPI
|
## XIVAPI
|
||||||
- Basis-URL: `https://v2.xivapi.com`
|
- Basis-URL: `https://v2.xivapi.com`
|
||||||
@ -174,7 +207,8 @@ Redirect URI in FFLogs App und config.php: `http://localhost:8080/auth/callback.
|
|||||||
|
|
||||||
## Bekannte Schema-Infos (ReportFight)
|
## Bekannte Schema-Infos (ReportFight)
|
||||||
Verfügbare aber noch nicht genutzte Felder: `friendlyPlayers`, `enemyNPCs`,
|
Verfügbare aber noch nicht genutzte Felder: `friendlyPlayers`, `enemyNPCs`,
|
||||||
`lastPhase`, `standardComposition`, `hasEcho`, `combatTime`, `phaseTransitions`
|
`lastPhase`, `standardComposition`, `hasEcho`, `combatTime`
|
||||||
|
Genutzt: `phaseTransitions` (für Phase-Filter), `fightPercentage`, `kill`, `startTime`, `endTime`
|
||||||
Vollständiges Schema: siehe `debug/schema.php` oder `fflogs-schema.json`
|
Vollständiges Schema: siehe `debug/schema.php` oder `fflogs-schema.json`
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|||||||
@ -106,6 +106,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.aoe-ability {
|
.aoe-ability {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: var(--t1);
|
color: var(--t1);
|
||||||
margin-bottom: 7px;
|
margin-bottom: 7px;
|
||||||
|
|||||||
@ -310,6 +310,32 @@
|
|||||||
);
|
);
|
||||||
if (!visibleTargets.length) return '';
|
if (!visibleTargets.length) return '';
|
||||||
|
|
||||||
|
// Collect boss debuffs (Reprisal/Feint/Addle) once at event level
|
||||||
|
const seenDebuffNames = new Set();
|
||||||
|
const eventDebuffs = [];
|
||||||
|
for (const t of visibleTargets) {
|
||||||
|
for (const m of (t.mitigations ?? [])) {
|
||||||
|
if (m.buffType === 'debuff' && !seenDebuffNames.has(m.name)) {
|
||||||
|
seenDebuffNames.add(m.name);
|
||||||
|
eventDebuffs.push(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const eventMissingDebuffs = refEv
|
||||||
|
? (refEv.targets[0]?.mitigations ?? []).filter(m => m.buffType === 'debuff' && !seenDebuffNames.has(m.name))
|
||||||
|
: [];
|
||||||
|
const debuffIconsHtml = [
|
||||||
|
...eventDebuffs.map(m => ({ ...m, missing: false })),
|
||||||
|
...eventMissingDebuffs.map(m => ({ ...m, missing: true })),
|
||||||
|
].map(m => {
|
||||||
|
const iconSrc = MITIG_ICONS[m.name];
|
||||||
|
if (!iconSrc) return '';
|
||||||
|
const dr = m.dr > 0 ? ` −${m.dr}%` : '';
|
||||||
|
return m.missing
|
||||||
|
? `<img class="aoe-target-buff-icon aoe-buff-missing" src="${iconSrc}" alt="${m.name}" title="${m.name}${dr} fehlt (war im Referenz-Pull aktiv)">`
|
||||||
|
: `<img class="aoe-target-buff-icon" src="${iconSrc}" alt="${m.name}" title="${m.name}${dr}">`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
// Current targets
|
// Current targets
|
||||||
const targets = visibleTargets.map(t => {
|
const targets = visibleTargets.map(t => {
|
||||||
const hpBar = (t.maxHp > 0) ? (() => {
|
const hpBar = (t.maxHp > 0) ? (() => {
|
||||||
@ -324,21 +350,21 @@
|
|||||||
|
|
||||||
const currentMitigNames = new Set((t.mitigations ?? []).map(m => m.name));
|
const currentMitigNames = new Set((t.mitigations ?? []).map(m => m.name));
|
||||||
const refTarget = refEv?.targets?.find(rt => rt.name === t.name);
|
const refTarget = refEv?.targets?.find(rt => rt.name === t.name);
|
||||||
const missingMitigNames = refTarget
|
const missingMitigs = refTarget
|
||||||
? new Set((refTarget.mitigations ?? []).filter(m => !currentMitigNames.has(m.name)).map(m => m.name))
|
? (refTarget.mitigations ?? []).filter(m => m.buffType !== 'debuff' && !currentMitigNames.has(m.name))
|
||||||
: new Set();
|
: [];
|
||||||
|
|
||||||
const mitigIcons = (t.mitigations ?? []).map(m => {
|
const mitigIcons = (t.mitigations ?? []).filter(m => m.buffType !== 'debuff').map(m => {
|
||||||
const iconSrc = MITIG_ICONS[m.name];
|
const iconSrc = MITIG_ICONS[m.name];
|
||||||
if (!iconSrc) return '';
|
if (!iconSrc) return '';
|
||||||
const dr = m.dr > 0 ? ` −${m.dr}%` : '';
|
const dr = m.dr > 0 ? ` −${m.dr}%` : '';
|
||||||
return `<img class="aoe-target-buff-icon" src="${iconSrc}" alt="${m.name}" title="${m.name}${dr}">`;
|
return `<img class="aoe-target-buff-icon" src="${iconSrc}" alt="${m.name}" title="${m.name}${dr}">`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
const missingIcons = [...missingMitigNames].map(name => {
|
const missingIcons = missingMitigs.map(m => {
|
||||||
const iconSrc = MITIG_ICONS[name];
|
const iconSrc = MITIG_ICONS[m.name];
|
||||||
if (!iconSrc) return '';
|
if (!iconSrc) return '';
|
||||||
return `<img class="aoe-target-buff-icon aoe-buff-missing" src="${iconSrc}" alt="${name}" title="${name} fehlt (war im Referenz-Pull aktiv)">`;
|
return `<img class="aoe-target-buff-icon aoe-buff-missing" src="${iconSrc}" alt="${m.name}" title="${m.name} fehlt (war im Referenz-Pull aktiv)">`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
const dead = t.hp === 0 && t.maxHp > 0;
|
const dead = t.hp === 0 && t.maxHp > 0;
|
||||||
@ -373,6 +399,16 @@
|
|||||||
const currentByName = {};
|
const currentByName = {};
|
||||||
ev.targets.forEach(t => { currentByName[t.name] = t; });
|
ev.targets.forEach(t => { currentByName[t.name] = t; });
|
||||||
|
|
||||||
|
const seenRefDebuffNames = new Set();
|
||||||
|
const refDebuffIconsHtml = refVisible.flatMap(t => (t.mitigations ?? []))
|
||||||
|
.filter(m => m.buffType === 'debuff' && !seenRefDebuffNames.has(m.name) && seenRefDebuffNames.add(m.name))
|
||||||
|
.map(m => {
|
||||||
|
const iconSrc = MITIG_ICONS[m.name];
|
||||||
|
if (!iconSrc) return '';
|
||||||
|
const dr = m.dr > 0 ? ` −${m.dr}%` : '';
|
||||||
|
return `<img class="aoe-target-buff-icon" src="${iconSrc}" alt="${m.name}" title="${m.name}${dr}">`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
const refCards = refVisible.map(t => {
|
const refCards = refVisible.map(t => {
|
||||||
const curr = currentByName[t.name];
|
const curr = currentByName[t.name];
|
||||||
const diff = curr ? curr.amount - t.amount : 0;
|
const diff = curr ? curr.amount - t.amount : 0;
|
||||||
@ -382,7 +418,7 @@
|
|||||||
? `<span class="${diff > 0 ? 'aoe-delta-worse' : 'aoe-delta-better'}">${diff > 0 ? '+' : '-'}${fmtDmg(Math.abs(diff))}</span>`
|
? `<span class="${diff > 0 ? 'aoe-delta-worse' : 'aoe-delta-better'}">${diff > 0 ? '+' : '-'}${fmtDmg(Math.abs(diff))}</span>`
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
const refMitigIcons = (t.mitigations ?? []).map(m => {
|
const refMitigIcons = (t.mitigations ?? []).filter(m => m.buffType !== 'debuff').map(m => {
|
||||||
const iconSrc = MITIG_ICONS[m.name];
|
const iconSrc = MITIG_ICONS[m.name];
|
||||||
if (!iconSrc) return '';
|
if (!iconSrc) return '';
|
||||||
const dr = m.dr > 0 ? ` −${m.dr}%` : '';
|
const dr = m.dr > 0 ? ` −${m.dr}%` : '';
|
||||||
@ -410,7 +446,7 @@
|
|||||||
|
|
||||||
refHtml = `
|
refHtml = `
|
||||||
<div class="aoe-ref-row">
|
<div class="aoe-ref-row">
|
||||||
<span class="aoe-ref-label">REF ${fmtDmg(refEv.totalDamage)} ${totalDelta}</span>
|
<span class="aoe-ref-label">REF ${fmtDmg(refEv.totalDamage)} ${totalDelta} ${refDebuffIconsHtml}</span>
|
||||||
<div class="aoe-targets">${refCards}</div>
|
<div class="aoe-targets">${refCards}</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
@ -423,6 +459,7 @@
|
|||||||
<div class="aoe-ability">
|
<div class="aoe-ability">
|
||||||
${ev.abilityName}
|
${ev.abilityName}
|
||||||
<span class="aoe-total">— ${fmtDmg(ev.totalDamage)} total</span>
|
<span class="aoe-total">— ${fmtDmg(ev.totalDamage)} total</span>
|
||||||
|
${debuffIconsHtml}
|
||||||
</div>
|
</div>
|
||||||
<div class="aoe-targets">${targets}</div>
|
<div class="aoe-targets">${targets}</div>
|
||||||
${refHtml}
|
${refHtml}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user