Two-tab app: report viewer + analysis tab with AoE timeline, per-player mitigation icons (local XIVAPI PNGs), and fight-wide buff/debuff window tracking. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
8.7 KiB
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, Raw-JSON-Ausgabe
- Analyse-Tab: Spielerübersicht + AoE-Timeline mit Mitigation-Tracking
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 inpage.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:
window.App = { reportCode, fightId, fightStart, fightEnd }
window.analysisTab (definiert in analysis.js) stellt Hooks bereit:
onFightSelected()— wird vonapp.jsaufgerufen wenn ein Fight gewählt wirdonTabOpen()— wird vontabs.jsaufgerufen wenn der Analyse-Tab geöffnet wirdreset()— wird vonapp.jsaufgerufen 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, output-card
tab-analysis.php — Analyse-Tab: Spieler-Grid + AoE-Timeline HTML
report-form.php — Report-Code-Eingabe Card
fight-select.php — Fight-Auswahl Dropdown Card
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
js/
app.js — Formular, Fight-Dropdown, Fetch, window.App State
tabs.js — Tab-Switching, ruft window.analysisTab.onTabOpen() auf
analysis.js — Analyse-Tab: Daten laden, Spieler rendern, Timeline rendern
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
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-dCinzel (Titel/Logo),--font-bInter (Body) - Border-Radius:
--r(klein),--rl(groß)
Analyse-Tab — Konzepte & Entscheidungen
AoE-Erkennung
- Nur
calculateddamageEvents (post-Mitigation Snapshot) — nichtdamage(Application), da sonst doppelte Events - Gruppierung: 300ms-Zeitfenster ×
abilityGameID→ Bucket - AoE = Bucket mit ≥ 3 unterschiedlichen
targetIDs - Auto-Attacks und Fähigkeiten mit
abilityGameID ≤ 7werden gefiltert - Tick-Schaden (
ev['tick'] = true) wird ignoriert
Spielernamen statt IDs
masterData.abilities(gameID → Name) wird zusammen mitplayerDetailsin 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 |
| Reprisal | 10% | debuff |
| Feint | 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.
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_ICONSObjekt (Ability-Name → lokaler Pfad) - Darstellung: 16×16px Icon unter jedem Spieler-Target, kein Text,
title-Tooltip mit Name + DR% + Caster
Geplante Features
HP-Balken pro Spieler (nächstes Feature)
Für jeden Spieler in der AoE-Timeline einen 3-Segment-Balken anzeigen, der den HP-Stand im Kontext des Treffers zeigt:
[████████████▓▓▓░░░░░░░░]
^verbleibend ^Schaden ^vorher schon fehlend
- Grün (links): HP nach dem Treffer (als % von MaxHP)
- Rot/Orange (Mitte): erlittener Schaden (als % von MaxHP)
- Dunkelgrau (rechts): HP die bereits vor dem Treffer fehlten
- Grünton dynamisch: >50% grün, 25–50% gelb/amber, <25% rot
- Balken sitzt direkt unter Name+Schaden-Zeile in der Spieler-Box
Datenbedarf: FFLogs calculateddamage Events enthalten hitPoints (HP vor dem Treffer) und maxHitPoints. Diese müssen in api/analysis.php pro Target mitgegeben werden.
Backend-Änderung: In $buckets[$key]['targets'][$tgtId] zusätzlich hp und maxHp aus dem letzten Event speichern und in der Response durchreichen.
Konfiguration
config.php:CLIENT_ID,REDIRECT_URI,DEV_MODEanpassenDEV_MODE = truedeaktiviert SSL-Verifizierung (nur lokal, nie in Produktion)session.cookie_secureist beiDEV_MODEautomatischfalse
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:
calculateddamage(Snapshot nach Mitigation, den wir nutzen) vs.damage(Application, ignorieren)
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, phaseTransitions
Vollständiges Schema: siehe debug/schema.php oder fflogs-schema.json
Deployment
DEV_MODEauffalsesetzenREDIRECT_URIauf produktive HTTPS-URL anpassendebug/Ordner nicht deployenassets/Ordner deployen (enthält lokale Icons)