ff14-mitigator/api/debug-events.php
xziino 89d1ac0df1 Event Explorer: masterData.abilities.type + ABILITY_TYPE_PHYSICAL/MAGICAL Konstanten
- debug-events.php: masterData { abilities { gameID name type } } mitabfragen,
  Ergebnis als ability_meta-Tabelle + _ability-Feld pro Event im Output
- ffxiv-data.js: ABILITY_TYPE_PHYSICAL (128) / ABILITY_TYPE_MAGICAL (1024)
  als verifizierte Bitmask-Konstanten + abilityTypeIsPhysical/abilityTypeIsMagical
  Hilfsfunktionen -- Grundlage fuer dynamische Feint/Addle DR-Berechnung

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 12:39:36 +02:00

159 lines
5.7 KiB
PHP

<?php
ini_set('display_errors', '0');
require_once __DIR__ . '/../config.php';
session_start_safe();
header('Content-Type: application/json');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); echo json_encode(['error' => 'Method not allowed']); exit; }
if (empty($_SESSION['access_token'])) { echo json_encode(['reauth' => true]); exit; }
if (($_SESSION['token_expires'] ?? 0) <= time()) { echo json_encode(['reauth' => true]); exit; }
$reportCode = preg_replace('/[^a-zA-Z0-9]/', '', $_POST['report_code'] ?? '');
$fightId = (int)($_POST['fight_id'] ?? 0);
$startTime = (float)($_POST['start_time'] ?? 0);
$endTime = (float)($_POST['end_time'] ?? 0);
$playerName = trim($_POST['player_name'] ?? '');
$eventType = trim($_POST['event_type'] ?? '');
$abilityId = (int)($_POST['ability_id'] ?? 0);
$limit = max(1, min(500, (int)($_POST['limit'] ?? 20)));
$language = strtolower(trim($_POST['language'] ?? 'en'));
$language = in_array($language, ['en', 'de', 'fr', 'jp'], true) ? $language : 'en';
$translate = 'true';
$startOffset = (float)($_POST['start_offset'] ?? 0) * 1000; // s → ms
$endOffset = isset($_POST['end_offset']) && $_POST['end_offset'] !== ''
? (float)$_POST['end_offset'] * 1000
: null;
$allowedTypes = ['DamageTaken', 'DamageDone', 'Healing', 'Casts', 'Buffs', 'Deaths'];
$dataType = in_array($_POST['data_type'] ?? '', $allowedTypes) ? $_POST['data_type'] : 'DamageTaken';
if (!$reportCode || !$fightId) { http_response_code(400); echo json_encode(['error' => 'Missing params']); exit; }
$queryStart = $startTime + $startOffset;
$queryEnd = $endOffset !== null ? $startTime + $endOffset : $endTime;
$queryEnd = min($queryEnd, $endTime);
$token = $_SESSION['access_token'];
function localized_graphql_uri(string $language): string {
$host = [
'de' => 'de.fflogs.com',
'fr' => 'fr.fflogs.com',
'jp' => 'ja.fflogs.com',
][$language] ?? 'www.fflogs.com';
return preg_replace('#https://[^/]+#', 'https://' . $host, GRAPHQL_URI);
}
function dbg_gql(string $query): array {
global $token, $language;
$acceptLanguage = $language === 'jp' ? 'ja' : $language;
$ch = curl_init(localized_graphql_uri($language));
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(['query' => $query]),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Authorization: Bearer ' . $token, 'Accept-Language: ' . $acceptLanguage],
CURLOPT_SSL_VERIFYPEER => !DEV_MODE,
]);
$body = curl_exec($ch);
curl_close($ch);
return json_decode($body, true) ?? [];
}
// Resolve player name → actor IDs (source + target)
$playerIds = [];
if ($playerName !== '') {
$pd = dbg_gql(<<<GQL
{
reportData {
report(code: "$reportCode") {
playerDetails(fightIDs: [$fightId])
}
}
}
GQL);
$pdRaw = $pd['data']['reportData']['report']['playerDetails'] ?? null;
$pdParsed = is_string($pdRaw) ? json_decode($pdRaw, true) : $pdRaw;
$pdGroups = $pdParsed['data']['playerDetails'] ?? [];
foreach (['tanks', 'healers', 'dps'] as $group) {
foreach ($pdGroups[$group] ?? [] as $p) {
if (stripos($p['name'], $playerName) !== false) {
$playerIds[] = (int)$p['id'];
}
}
}
}
// Fetch events + masterData abilities (incl. type)
$includeResources = in_array($dataType, ['DamageTaken', 'DamageDone']) ? 'includeResources: true,' : '';
$result = dbg_gql(<<<GQL
{
reportData {
report(code: "$reportCode") {
masterData(translate: false) {
abilities {
gameID
name
type
}
}
events(
fightIDs: [$fightId],
dataType: $dataType,
$includeResources
translate: $translate,
startTime: $queryStart,
endTime: $queryEnd
) {
data
}
}
}
}
GQL);
$events = $result['data']['reportData']['report']['events']['data'] ?? [];
// Build abilityGameID → { name, type } lookup
$abilityMeta = [];
foreach ($result['data']['reportData']['report']['masterData']['abilities'] ?? [] as $ab) {
$abilityMeta[(int)$ab['gameID']] = ['name' => $ab['name'], 'type' => $ab['type'] ?? null];
}
// Filter by raw event type, player (source OR target), then apply limit
// Enrich each event with ability meta (name + type) from masterData
$filtered = [];
foreach ($events as $ev) {
if ($eventType !== '' && ($ev['type'] ?? '') !== $eventType) continue;
if ($abilityId > 0 && (int)($ev['abilityGameID'] ?? 0) !== $abilityId) continue;
if (!empty($playerIds)) {
$srcId = (int)($ev['sourceID'] ?? -1);
$tgtId = (int)($ev['targetID'] ?? -1);
if (!in_array($srcId, $playerIds) && !in_array($tgtId, $playerIds)) continue;
}
// Inject ability meta so ability.type is visible directly on the event
$gid = (int)($ev['abilityGameID'] ?? 0);
if ($gid && isset($abilityMeta[$gid])) {
$ev['_ability'] = $abilityMeta[$gid];
}
$filtered[] = $ev;
if (count($filtered) >= $limit) break;
}
echo json_encode([
'data_type' => $dataType,
'event_type' => $eventType ?: null,
'ability_id' => $abilityId ?: null,
'player_name' => $playerName ?: null,
'player_ids' => $playerIds ?: null,
'time_range' => ['from_ms' => (int)$queryStart, 'to_ms' => (int)$queryEnd],
'total_before_limit' => count($events),
'count' => count($filtered),
'ability_meta' => $abilityMeta, // vollständige Lookup-Tabelle: gameID → {name, type}
'events' => $filtered,
], JSON_PRETTY_PRINT);