12 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 → 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 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, phases: [], fights: [] }
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 wirdonFightsLoaded(fights)— wird vonapp.jsaufgerufen nach Report-Load (befüllt Vergleichs-Dropdown)reset()— 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, 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-dCinzel (Titel/Logo),--font-bInter (Body) - Border-Radius:
--r(klein),--rl(groß)
Analyse-Tab — Konzepte & Entscheidungen
AoE-Erkennung
- Verwendet
damageEvents mitincludeResources: true(enthältbuffs-Feld für Mitigations) - Tick-Schaden (
ev['tick'] = true) undabilityGameID ≤ 7(Auto-Attacks) werden gefiltert - Proximity Clustering statt fixer Zeitfenster: Events werden pro
abilityGameIDgesammelt, 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
targetIDs - Pro Target werden
amount,absorbed,overkill,hp(nach Treffer),maxHpundbuffsgespeichert; bei mehreren Hits desselben Targets pro Cluster werdenamount/absorbed/overkillsummiert
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 |
| 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 abilityGameIDs 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_ICONSObjekt (Ability-Name → lokaler Pfad)
Darstellungsort nach buffType:
- Buffs (
buffType: 'buff'): erscheinen per Spieler unter der Spieler-Box (.aoe-target-buffs) - Debuffs (
buffType: 'debuff'): erscheinen einmal pro Event im Ability-Header neben "X total" bzw. in der REF-Zeile neben "REF X total ±delta" - Fehlende Mitigations aus dem Ref-Pull: Buffs → ausgegraut per Spieler; Debuffs → ausgegraut im jeweiligen Header
.aoe-abilitynutztdisplay: flex; align-items: center; gap: 6px— identisches Spacing wie.aoe-ref-label
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 austargetResources.hitPoints/maxHitPointsviaincludeResources: true
Death-Highlight + Absorbed/Overkill
- Spieler-Box bekommt Klasse
.aoe-target--deadwennhp === 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
phaseTransitionsaus dem gewählten Fight-Objekt (window.App.phases) - Dropdown
#phase-selecterscheint nur wenn Phasen vorhanden sind - Option "Ganzer Fight" (id=0) ist immer dabei; individuelle Phasen ab id=1
phaseFilter = { startTime, endTime }filtert die Timeline inrenderTimeline()
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-selectwird nach Report-Load mit allen Fights befüllt (viaonFightsLoaded) - Bei Auswahl: separater
api/analysis.php-Call für den Ref-Fight →refEvents[] - In
renderTimeline(): perabilityNameund Occurrence-Index gematchter Ref-Event wird alsREF-Zeile unterhalb der aktuellen Targets gerendert - Fehlende Mitigations (vorhanden im Ref, nicht im aktuellen Pull) werden als ausgegrautem Icon mit Klasse
.aoe-buff-missinggezeigt — Buffs per Spieler, Debuffs im Ability-Header - Schaden-Delta pro Spieler: grün wenn besser (
aoe-delta-better), rot wenn schlechter (aoe-delta-worse) - Gesamt-Delta + Ref-Debuff-Icons in der REF-Headerzeile (
aoe-ref-label)
Cross-Report-Vergleich
- Button "+ Anderer Report" (
#ref-ext-toggle) öffnet Panel mit Eingabefeld + Laden-Button api/fight.phpwird 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_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:
damage(post-Mitigation Snapshot, enthältbuffs-Feld — den wir nutzen) vs.calculateddamage(Application-Zeitpunkt, ignorieren) includeResources: trueimevents()-Query lieferttargetResources.hitPoints/maxHitPointspro 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_MODEauffalsesetzenREDIRECT_URIauf produktive HTTPS-URL anpassendebug/Ordner nicht deployenassets/Ordner deployen (enthält lokale Icons)