forked from xziino/ff14-mitigator
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>
219 lines
12 KiB
Markdown
219 lines
12 KiB
Markdown
# ff14-mitigator — FFLogs Mitigation Analyzer
|
||
|
||
## Projekt
|
||
PHP/HTML/JS-Tool zum Analysieren von FFXIV-Raidlogs via FFLogs OAuth2 PKCE + GraphQL API.
|
||
Kein Framework, kein Composer, kein npm — Plain PHP für Shared Hosting.
|
||
|
||
Zwei Tabs:
|
||
- **Report-Tab**: Report-Code eingeben, Fight auswählen → Fight-JSON-Ausgabe + Event Explorer
|
||
- **Analyse-Tab**: Spielerübersicht + AoE-Timeline mit Mitigation-Tracking, Pull-Vergleich, Phase-Filter
|
||
|
||
## Architektur & Konventionen
|
||
|
||
### Trennung von PHP, HTML und JS
|
||
- **PHP-Logik** gehört ausschließlich in `index.php` (und API/Auth-Endpunkte). Keine Geschäftslogik in Templates.
|
||
- **HTML** gehört in `templates/`. Jede logisch in sich geschlossene Komponente ist eine eigene Datei.
|
||
- **CSS** gehört in `css/`. Jede CSS-Datei hat einen klar abgegrenzten Scope (base, layout, components, analysis).
|
||
- **JavaScript** gehört in `js/`. Keine Inline-Scripts in Templates außer dem `<script src="...">` Tag in `page.php`.
|
||
|
||
### Template-System
|
||
`index.php` setzt alle Variablen und ruft dann `require templates/page.php` auf. Templates sind reine Ausgabe — sie lesen Variablen aus dem Scope, setzen aber keine.
|
||
|
||
### Geteilter JS-State
|
||
`window.App` in `app.js` hält den gemeinsamen State für alle Tabs:
|
||
```js
|
||
window.App = { reportCode, fightId, fightStart, fightEnd, phases: [], fights: [] }
|
||
```
|
||
`window.analysisTab` (definiert in `analysis.js`) stellt Hooks bereit:
|
||
- `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
|
||
- `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
|
||
|
||
## Dateistruktur
|
||
```
|
||
index.php — PHP-Logik: Auth-Check, Variablen, require page.php
|
||
config.php — Konstanten (CLIENT_ID, URIs) + session_start_safe()
|
||
templates/
|
||
page.php — HTML-Skeleton (head, body), routet zu login oder app
|
||
login.php — Login-Overlay (nicht authentifiziert / Token abgelaufen)
|
||
topbar.php — Topbar mit Logo + Tab-Navigation + Token-Ablaufzeit
|
||
tab-report.php — Report-Tab: includes report-form, fight-select, event-explorer, output-card
|
||
tab-analysis.php — Analyse-Tab: Spieler-Grid + Pull-Vergleich + AoE-Timeline HTML
|
||
report-form.php — Report-Code-Eingabe 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
|
||
css/
|
||
base.css — CSS-Variablen, Reset, Basis-Styles, Feedback-Klassen
|
||
layout.css — App-Shell, Topbar, Tabs, Login-Overlay, Form-Helpers
|
||
components.css — Cards, Inputs, Buttons, Badges, Terminal
|
||
analysis.css — Spieler-Grid, AoE-Timeline, Mitigation-Icons, HP-Bar, Ref-Row
|
||
js/
|
||
app.js — Formular, Fight-Dropdown, Fetch, window.App State, Event Explorer
|
||
tabs.js — Tab-Switching, ruft window.analysisTab.onTabOpen() auf
|
||
analysis.js — Analyse-Tab: Daten laden, Spieler rendern, Timeline rendern, Pull-Vergleich
|
||
auth/
|
||
start.php — PKCE generieren, Session speichern, Redirect zu FFLogs
|
||
callback.php — Code gegen Token tauschen, Token in Session speichern
|
||
api/
|
||
fight.php — POST-Endpunkt: Fight-Liste via GraphQL → 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/
|
||
icons/mitigation/ — Lokal gespeicherte Ability-Icons (PNG, von XIVAPI)
|
||
debug/
|
||
schema.php — Einmaliges Schema-Explorer Tool (nicht produktiv deployen)
|
||
```
|
||
|
||
## Design-System
|
||
CSS-Variablen in `css/base.css`:
|
||
- Hintergründe: `--bg0` (#08090d) bis `--bg3` (#1c2130), `--bgcard` (#121620)
|
||
- Akzentfarbe: `--gold` (#c8a84b)
|
||
- Text: `--t1` (hell) / `--t2` (gedimmt) / `--t3` (sehr gedimmt)
|
||
- Farben: `--blue`, `--green`, `--red`, `--orange`
|
||
- Fonts: `--font-d` Cinzel (Titel/Logo), `--font-b` Inter (Body)
|
||
- Border-Radius: `--r` (klein), `--rl` (groß)
|
||
|
||
## Analyse-Tab — Konzepte & Entscheidungen
|
||
|
||
### AoE-Erkennung
|
||
- Verwendet `damage` Events mit `includeResources: true` (enthält `buffs`-Feld für Mitigations)
|
||
- Tick-Schaden (`ev['tick'] = true`) und `abilityGameID ≤ 7` (Auto-Attacks) werden gefiltert
|
||
- **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
|
||
- AoE = Cluster mit ≥ 3 unterschiedlichen `targetID`s
|
||
- 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
|
||
- `masterData.abilities` (gameID → Name) wird zusammen mit `playerDetails` in einem einzigen Query abgerufen
|
||
- `$mitigIdMap` (gameID → Mitigation-Meta) wird nur für Abilities gebaut, die tatsächlich im Report vorkommen
|
||
|
||
### Mitigation-Tracking
|
||
Getrackte party-wide Buffs + Boss-Debuffs (definiert in `MITIGATION_ABILITIES` in `api/analysis.php`):
|
||
|
||
| Ability | DR | Typ |
|
||
|---|---|---|
|
||
| Passage of Arms | 15% | buff |
|
||
| Divine Veil | Barrier | buff |
|
||
| Shake It Off | Barrier | buff |
|
||
| Dark Missionary | 10% | buff |
|
||
| Heart of Light | 10% | buff |
|
||
| Temperance | 10% | buff |
|
||
| Sacred Soil | 10% | buff |
|
||
| Expedient | 10% | buff |
|
||
| Fey Illumination | 5% | buff |
|
||
| Collective Unconscious | 10% | buff |
|
||
| Holos | 10% | buff |
|
||
| Kerachole | 10% | buff |
|
||
| Panhaima | Barrier | buff |
|
||
| Troubadour | 15% | buff |
|
||
| Tactician | 15% | buff |
|
||
| Shield Samba | 15% | buff |
|
||
| Magick Barrier | 10% | buff |
|
||
| Reprisal | 10% | debuff |
|
||
| Feint | 10% | debuff |
|
||
| Addle | 10% | debuff |
|
||
|
||
**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
|
||
- Icons lokal gespeichert in `assets/icons/mitigation/` als PNG
|
||
- Quelle: XIVAPI v2 (`https://v2.xivapi.com/api/asset?path=...&format=png`)
|
||
- Icon-Pfade per Action-Row-ID abgerufen: `https://v2.xivapi.com/api/sheet/Action/{id}?fields=Name,Icon`
|
||
- Dateinamen: kebab-case des Ability-Namens (z.B. `passage-of-arms.png`)
|
||
- 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
|
||
|
||
## Implementierte Features (Übersicht neuerer Commits)
|
||
|
||
### HP-Balken pro Spieler
|
||
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`
|
||
- Overkill-Betrag wird links neben dem Job-Badge angezeigt (`.aoe-target-overkill`)
|
||
- Absorbed-Betrag erscheint als gedimmter Zusatz neben dem Schaden (`.aoe-target-absorbed`)
|
||
|
||
### Phase-Filter
|
||
- Liest `phaseTransitions` aus dem gewählten Fight-Objekt (`window.App.phases`)
|
||
- Dropdown `#phase-select` erscheint nur wenn Phasen vorhanden sind
|
||
- Option "Ganzer Fight" (id=0) ist immer dabei; individuelle Phasen ab id=1
|
||
- `phaseFilter = { startTime, endTime }` filtert die Timeline in `renderTimeline()`
|
||
|
||
### 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.
|
||
|
||
### 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
|
||
- `config.php`: `CLIENT_ID`, `REDIRECT_URI`, `DEV_MODE` anpassen
|
||
- `DEV_MODE = true` deaktiviert SSL-Verifizierung (nur lokal, nie in Produktion)
|
||
- `session.cookie_secure` ist bei `DEV_MODE` automatisch `false`
|
||
|
||
## FFLogs API
|
||
- OAuth2 PKCE (kein Client Secret, öffentliche App)
|
||
- App registrieren: https://www.fflogs.com/api/clients/
|
||
- GraphQL Endpoint (user-scoped): `https://www.fflogs.com/api/v2/user`
|
||
- Token Endpoint: `https://www.fflogs.com/oauth/token`
|
||
- Kein Refresh Token für öffentliche Clients — abgelaufene Sessions starten PKCE neu
|
||
- 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
|
||
- Basis-URL: `https://v2.xivapi.com`
|
||
- Action-Lookup per Row-ID: `/api/sheet/Action/{id}?fields=Name,Icon`
|
||
- Asset-Download: `/api/asset?path={tex_path}&format=png`
|
||
- Icons nicht hotlinken — lokal speichern (Community-Service, kein SLA)
|
||
- XIVAPI-Suche (`/api/search`) gibt bei manchen Abilities ClassJob-Daten statt Action-Daten zurück → direkt per Row-ID abrufen
|
||
|
||
## Repository
|
||
- Remote: `https://git.epow0.org/xziino/ff14-mitigator`
|
||
- Platform: Gitea (git.epow0.org)
|
||
- Branch: `main`
|
||
|
||
## Lokale Entwicklung
|
||
```
|
||
php -S localhost:8080
|
||
```
|
||
Dann `http://localhost:8080` im Browser öffnen.
|
||
Redirect URI in FFLogs App und config.php: `http://localhost:8080/auth/callback.php`
|
||
|
||
## Bekannte Schema-Infos (ReportFight)
|
||
Verfügbare aber noch nicht genutzte Felder: `friendlyPlayers`, `enemyNPCs`,
|
||
`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`
|
||
|
||
## Deployment
|
||
- `DEV_MODE` auf `false` setzen
|
||
- `REDIRECT_URI` auf produktive HTTPS-URL anpassen
|
||
- `debug/` Ordner nicht deployen
|
||
- `assets/` Ordner deployen (enthält lokale Icons)
|