您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Improves FFlogs.
当前为
- // ==UserScript==
- // @name FF Progs
- // @name:en FF Progs
- // @description Improves FFlogs.
- // @description:en Improves FFlogs.
- // @version 1.0.9
- // @namespace k_fizzel
- // @author Chad Bradly
- // @website https://www.fflogs.com/character/id/12781922
- // @icon https://assets.rpglogs.com/img/ff/favicon.png?v=2
- // @match https://*.fflogs.com/*
- // @require https://code.jquery.com/jquery-3.2.0.min.js
- // @grant unsafeWindow
- // @grant GM_addStyle
- // @grant GM_getValue
- // @grant GM_setValue
- // @grant GM_deleteValue
- // @license MIT License
- // ==/UserScript==
- (function () {
- "use strict";
- const JOB_ORDER = [
- // Tanks
- "Paladin",
- "Warrior",
- "DarkKnight",
- "Gunbreaker",
- // Healers
- "WhiteMage",
- "Scholar",
- "Astrologian",
- "Sage",
- // Melee
- "Monk",
- "Dragoon",
- "Ninja",
- "Samurai",
- "Reaper",
- // Physical Ranged
- "Bard",
- "Machinist",
- "Dancer",
- // Magical Ranged
- "BlackMage",
- "Summoner",
- "RedMage",
- ];
- const ABILITY_TYPES = {
- 0: "None",
- 1: "Buff",
- 2: "Unknown",
- 4: "Unknown",
- 8: "Heal",
- 16: "Unknown",
- 32: "True",
- 64: "DOT",
- 124: "Darkness",
- 125: "Darkness",
- 126: "Darkness",
- 127: "Darkness",
- 128: "Physical",
- 256: "Magical",
- 512: "Unknown",
- 1024: "Magical",
- };
- const PASSIVE_LB_GAIN = [
- ["75"], // one bar
- ["180"], // two bars
- ["220", "170", "160", "154", "144", "140"], // three bars
- ];
- // this code was made in 1 day so its not the best but it works :D
- const LB_PIN = `2$Main$#ffff14$script$let l;pinMatchesFightEvent=(e,f)=>{switch(e.type){case"limitbreakupdate":return l&&l===e.timestamp||(l=e.timestamp),!0;case"calculateddamage":if("Player"===e.target.type&&e.timestamp===l)return!0;break;case"heal":if(!e.isTick&&e.timestamp===l)return!0}return!1};`;
- const REPORTS_PATH_REGEX = /\/reports\/.+/;
- const ZONE_RANKINGS_PATH_REGEX = /\/zone\/rankings\/.+/;
- const CHARACTER_PATH_REGEX = /\/character\/.+/;
- const PROFILE_PATH_REGEX = /\/profile/;
- const LB_REGEX = /The limit break gauge updated to (\d+). There are (\d+) total bars./;
- const apiKey = GM_getValue("apiKey");
- const getHashParams = () => {
- const hash = window.location.hash.substring(1);
- const params = {};
- hash.split("&").forEach((pair) => {
- const [key, value] = pair.split("=");
- params[key] = decodeURIComponent(value);
- });
- return params;
- };
- const changeHashParams = (defaultParams) => {
- const hashParams = getHashParams();
- const newParams = {
- ...hashParams,
- ...defaultParams,
- };
- location.hash = Object.entries(newParams)
- .filter(([_key, value]) => !["undefined", "null", "", null, undefined].includes(value))
- .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
- .join("&");
- };
- const characterAllStar = (rank, outOf, rDPS, rankOneRDPS) => {
- return Math.min(Math.max(100 * (rDPS / rankOneRDPS), 100 - (rank / outOf) * 100) + 20 * (rDPS / rankOneRDPS), 120);
- };
- // AdBlock
- $("#top-banner, .side-rail-ads, #bottom-banner, #subscription-message-tile-container, #playwire-video-container, #right-ad-box, #right-vertical-banner, #gear-box-ad, .ad-placement-sticky-footer, .content-sidebar").remove();
- $(".content-with-sidebar").css("display", "block");
- $("#table-container").css("margin", "0 0 0 0");
- // Reports Page
- if (REPORTS_PATH_REGEX.test(location.pathname)) {
- // Add XIV Analysis Button
- $("#filter-analyze-tab").before(
- `<a target="_blank" class="big-tab view-type-tab" id="xivanalysis-tab"><span class="zmdi zmdi-time-interval"></span> <span class="big-tab-text"><br>xivanalysis</span></a>`
- );
- $("#xivanalysis-tab").click(() => {
- $("#xivanalysis-tab").attr("href", `https://xivanalysis.com/report-redirect/${location.href}`);
- });
- $("#filter-type-tabs").css("cursor", "default");
- // add new tab 1 before last element
- $("#filter-type-tabs").find("a:nth-last-child(2)").after(`<a href="#" class="filter-type-tab drop" id="filter-lb-tab">LB</a>`);
- $("#filter-lb-tab").click(() => {
- changeHashParams({
- type: "summary",
- view: "events",
- pins: LB_PIN,
- });
- return false;
- });
- let jobs;
- const rankOnes = {};
- const onTableChange = () => {
- const hashParams = getHashParams();
- let lastLbGain;
- let lastTimeDiff;
- // Rankings Tab
- if (hashParams.view === "rankings") {
- if (!GM_getValue("apiKey")) return;
- const rows = [];
- if (!jobs) {
- fetch(`https://www.fflogs.com/v1/classes?api_key=${GM_getValue("apiKey")}`)
- .then((res) => res.json())
- .then((data) => {
- jobs = data[0].specs;
- rows.forEach((row) => {
- updatePoints(row);
- });
- })
- .catch((err) => console.error(err));
- } else {
- setTimeout(() => {
- rows.forEach((row) => {
- updatePoints(row);
- });
- }, 0);
- }
- const updatePoints = async (row) => {
- const hashParams = getHashParams();
- const rank = Number($(row).find("td:nth-child(2)").text().replace("~", ""));
- const outOf = Number($(row).find("td:nth-child(3)").text().replace(",", ""));
- const dps = Number($(row).find("td:nth-child(6)").text().replace(",", ""));
- const jobName = $(row).find("td:nth-child(5) > a").attr("class") || "";
- const jobName2 = $(row).find("td:nth-child(5) > a:nth-last-child(1)").attr("class") || "";
- const playerMetric = hashParams.playermetric || "rdps";
- if (jobName2 !== "players-table-realm") {
- $(row)
- .find("td:nth-child(7)")
- .html(`<center><img src="https://cdn.7tv.app/emote/62523dbbbab59cfd1b8b889d/1x.webp" title="No api v1 endpoint for combined damage." style="height: 15px;"></center>`);
- return;
- }
- const updateCharecterAllStar = async () => {
- $(row).find("td:nth-child(7)").html(characterAllStar(rank, outOf, dps, rankOnes[jobName][playerMetric]).toFixed(2));
- };
- if (!rankOnes[jobName]) {
- rankOnes[jobName] = {};
- }
- if (!rankOnes[jobName][playerMetric]) {
- const url = `https://www.fflogs.com/v1/rankings/encounter/${reportsCache.filterFightBoss}?metric=${playerMetric}&spec=${
- jobs.find((job) => job.name.replace(" ", "") === jobName)?.id
- }&api_key=${GM_getValue("apiKey")}`;
- fetch(url)
- .then((res) => res.json())
- .then((data) => {
- rankOnes[jobName][playerMetric] = Number(data.rankings[0].total.toFixed(1));
- updateCharecterAllStar();
- })
- .catch((err) => console.error(err));
- } else {
- updateCharecterAllStar();
- }
- };
- $(".player-table").each((_i, table) => {
- $(table)
- .find("thead tr th:nth-child(6)")
- .after(
- `<th class="sorting ui-state-default" tabindex="0" aria-controls="DataTables_Table_0" rowspan="1" colspan="1" aria-label="Patch: activate to sort column ascending"><div class="DataTables_sort_wrapper">Points<span class="DataTables_sort_icon css_right ui-icon ui-icon-caret-2-n-s"></span></div></th>`
- );
- $(table)
- .find("tbody tr")
- .each((_i, row) => {
- $(row)
- .find("td:nth-child(6)")
- .after(`<td class="rank-per-second primary main-table-number"><center><span class="zmdi zmdi-spinner zmdi-hc-spin" style="color:white font-size:24px"></center></span></td>`);
- rows.push(row);
- });
- });
- }
- // Events Tab
- if (hashParams.view === "events") {
- if (hashParams.type === "resources") {
- return;
- }
- $(".events-table")
- .find("thead tr th:nth-child(1)")
- .before(`<th class="ui-state-default sorting_disabled" rowspan="1" colspan="1"><div class="DataTables_sort_wrapper">Diff<span class="DataTables_sort_icon"></span></div></th>`);
- $(".main-table-number").each((_i, cell) => {
- if (lastTimeDiff) {
- const time = moment($(cell).text(), "m:ss.SSS");
- const diff = (time.diff(lastTimeDiff) / 1000).toFixed(3);
- let bgColor = "";
- if (hashParams.type === "casts" && hashParams.source) {
- if (diff < 0.575) {
- bgColor = "background-color: orange !important;";
- }
- if (diff < 0.535) {
- bgColor = "background-color: chocolate !important;";
- }
- if (diff < 0.475) {
- bgColor = "background-color: red !important;";
- }
- if (diff < 0.435) {
- bgColor = "background-color: purple !important;";
- }
- }
- $(cell).before(`<td style="width: 2em; text-align: right; ${bgColor}">${diff.padStart(5, "0")}</td>`);
- lastTimeDiff = time;
- } else {
- $(cell).before(`<td style="width: 2em; text-align: right;"> - </td>`);
- lastTimeDiff = moment($(cell).text(), "m:ss.SSS");
- }
- });
- if (hashParams.type === "casts" && hashParams.hostility === "1") {
- $(".event-ability-cell a").each((_i, cell) => {
- const actionId = $(cell).attr("href").split("/")[5];
- console.log(actionId);
- const hexId = parseInt(actionId).toString(16);
- $(cell).text(`${$(cell).text()} [${hexId}]`);
- });
- }
- }
- // LB Tab
- if (hashParams.view === "events" && hashParams.type === "summary" && hashParams.pins === LB_PIN) {
- $(".filter-type-tab.selected").removeClass("selected");
- $("#filter-lb-tab").addClass("selected");
- $(".events-table")
- .find("thead tr th:nth-last-child(3)")
- .after(`<th class="ui-state-default sorting_disabled" rowspan="1" colspan="1"><div class="DataTables_sort_wrapper">Active<span class="DataTables_sort_icon"></span></div></th>`);
- $(".events-table")
- .find("thead tr th:nth-last-child(2)")
- .after(`<th class="ui-state-default sorting_disabled" rowspan="1" colspan="1"><div class="DataTables_sort_wrapper">Bars<span class="DataTables_sort_icon"></span></div></th>`);
- $(".event-description-cell").each((_i, cell) => {
- const text = $(cell).text();
- if (text === "Event") {
- $(cell).html(`<div class="DataTables_sort_wrapper">Limit Break Total<span class="DataTables_sort_icon"></span></div>`);
- return;
- }
- if (!LB_REGEX.test(text)) {
- $(cell).before(`<td style="width: 2em; text-align: right; white-space: nowrap;"> * </td>`);
- $(cell).after(`<td style="width: 2em; text-align: right;"> * </td>`);
- return;
- }
- const lb = text.match(LB_REGEX);
- const currentLb = Number(lb?.[1]);
- const currentBars = Number(lb?.[2]);
- if (lb) {
- let diff;
- if (lastLbGain !== undefined) {
- diff = (currentLb - lastLbGain).toLocaleString();
- } else {
- diff = " - ";
- }
- lastLbGain = currentLb;
- let actualDiff = diff > 0 ? `+${diff}` : diff;
- if (PASSIVE_LB_GAIN[currentBars - 1].includes(diff)) {
- // passive lb gain
- diff = " - ";
- } else {
- // active lb gain
- }
- $(cell).before(`<td style="width: 2em; text-align: right; white-space: nowrap;">${diff}</td>`);
- $(cell).html(`${Number(currentLb).toLocaleString()} / ${(Number(currentBars) * 10000).toLocaleString()} <span style="float: right;">${actualDiff}</span>`);
- $(cell).after(`<td style="width: 2em; text-align: right;">${currentBars}</td>`);
- }
- });
- } else if (hashParams.pins === LB_PIN) {
- $("#filter-lb-tab").removeClass("selected");
- $(`#filter-${hashParams.type}-tab`).addClass("selected");
- changeHashParams({ pins: "" });
- }
- };
- const tableContainer = document.querySelector("#table-container");
- if (tableContainer) {
- const observer = new MutationObserver(onTableChange);
- observer.observe(tableContainer, {
- attributes: true,
- characterData: true,
- childList: true,
- });
- }
- }
- // Zone Rankings Page
- if (ZONE_RANKINGS_PATH_REGEX.test(location.pathname)) {
- const onTableChange = () => {
- $(".main-table-name").each((_i, cell) => {
- if ($(cell).find(".main-table-realm").text().includes("(JP)")) {
- if ($(cell).find(".main-table-guild").attr("href").includes("translate=true")) return;
- $(cell)
- .find(".main-table-guild")
- .attr("href", `${$(cell).find(".main-table-guild").attr("href")}&translate=true`);
- }
- });
- };
- onTableChange();
- const tableContainer = document.querySelector("#table-container");
- if (tableContainer) {
- const observer = new MutationObserver(onTableChange);
- observer.observe(tableContainer, {
- attributes: true,
- characterData: true,
- childList: true,
- });
- }
- }
- // Character Page
- if (CHARACTER_PATH_REGEX.test(location.pathname)) {
- // Chad Bradly's Profile Customization
- const CHAD_ID_REGEX = /\/character\/id\/12781922/;
- const CHAD_NAME_REGEX = /\/character\/na\/sargatanas\/chad%20bradly/;
- const CHAD_ICON_URL = "https://media.tenor.com/epNMHGvRyHcAAAAd/gigachad-chad.gif";
- if (CHAD_ID_REGEX.test(location.pathname) || CHAD_NAME_REGEX.test(location.pathname)) {
- $("#character-portrait-image").attr("src", CHAD_ICON_URL);
- }
- }
- // Profile Page
- if (PROFILE_PATH_REGEX.test(location.pathname)) {
- const $extension = $(`
- <div id="extension" class="dialog-block">
- <div id="extension-title" class="dialog-title">FF Progs</div>
- <div id="extension-content" style="margin:1em"></div>
- </div>
- `);
- const $apiInputContainer = $(`
- <div id="api-input-container" style="margin:1em">
- <div>Enter your FFLogs API Key</div>
- <input type="text" id="api-key-input" style="margin-left: 10px" value="${apiKey || ""}">
- <input type="button" id="api-save-button" style="margin-left: 10px" value="${apiKey ? "Update API Key" : "Save API Key"}">
- </div>
- `);
- const $apiStatus = $(`
- <div id="api-status" style="margin:1em; display: ${apiKey ? "block" : "none"}">
- <div>API Key ${apiKey ? "saved" : "not saved"}</div>
- <input type="button" id="api-remove-button" style="margin-left: 10px" value="Remove API Key">
- </div>
- `);
- const saveApiKey = () => {
- const newApiKey = $("#api-key-input").val().trim();
- if (newApiKey) {
- GM_setValue("apiKey", newApiKey);
- $apiStatus.show().find("div").text("API Key saved");
- $apiInputContainer.hide();
- setTimeout(() => {
- $apiStatus.hide();
- $apiInputContainer.show();
- }, 2000);
- }
- };
- const removeApiKey = () => {
- GM_deleteValue("apiKey");
- $apiStatus.show().find("div").text("API Key removed");
- $apiStatus.find("#api-remove-button").remove();
- $apiInputContainer.show();
- setTimeout(() => {
- $apiStatus.hide();
- }, 2000);
- };
- $extension.insertAfter("#api");
- $apiInputContainer.appendTo("#extension-content");
- $apiStatus.appendTo("#extension-content");
- $apiInputContainer.on("click", "#api-save-button", saveApiKey);
- $apiStatus.on("click", "#api-remove-button", removeApiKey);
- }
- })();