Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

MediaWiki:Common.js: Difference between revisions

MediaWiki interface page
No edit summary
No edit summary
 
(10 intermediate revisions by the same user not shown)
Line 8: Line 8:




/* ===========================
mw.hook('wikipage.content').add(function () {
  Dungeon Speedrun Leaderboard
  =========================== */


mw.hook('wikipage.content').add(function () {
  /* ===========================
    WAIT FOR TABLE (IMPORTANT)
    =========================== */


   function parseTime(t) {
   function waitForTable(callback) {
     if (!t || !t.includes(":")) return 999999;
     const table = document.getElementById("leaderboardTable");
     const parts = t.trim().split(":");
     if (table) {
     return parseInt(parts[0], 10) * 60 + parseInt(parts[1], 10);
      callback(table);
     } else {
      setTimeout(() => waitForTable(callback), 50);
    }
   }
   }




   /* ===========================
   waitForTable(function(table) {
    SOLO TABLE
    =========================== */


  const table = document.getElementById("leaderboardTable");
    /* ===========================
      HELPERS
      =========================== */


  if (table) {
    function parseTime(t) {
      if (!t || !t.includes(":")) return 999999;
      const [m, s] = t.trim().split(":").map(Number);
      return m * 60 + s;
    }


     const container = document.getElementById("filterContainer");
     function getPoints(rank) {
      const scoring = {
        1:25,2:20,3:17,4:15,5:13,
        6:11,7:10,8:9,9:8,10:7,
        11:6,12:5,13:4,14:3,15:2
      };
      return scoring[rank] || 1;
    }


    if (container) {


      const wrapper = document.createElement("div");
    /* ===========================
      wrapper.style.marginBottom = "15px";
      FILTER UI
      =========================== */


      const dungeonLabel = document.createElement("strong");
    const filterWrapper = document.createElement("div");
      dungeonLabel.innerText = "Dungeon: ";
    filterWrapper.style.marginBottom = "20px";
      wrapper.appendChild(dungeonLabel);


      const dungeonSelect = document.createElement("select");
    const filterTitle = document.createElement("h3");
      dungeonSelect.style.marginRight = "20px";
    filterTitle.innerText = "🔍 Filter Runs";
    filterWrapper.appendChild(filterTitle);


      const dungeons = [
    const dungeonSelect = document.createElement("select");
        "All",
    dungeonSelect.style.marginRight = "10px";
        "Wolf's Den","Wolf's Den II","Wolf's Den III",
        "The Red Nest","For The Eggs",
        "Scarlet Revenge","Scarlet Revenge II",
        "Promised One","Promised One II","Promised One III",
        "Lost and Found","The Hostage","Cactus On Fire",
        "Deep Echoes","Deep Echoes II","Deep Echoes III",
        "King's Hoard","King's Hoard II",
        "Hall of Daggers"
      ];


       dungeons.forEach(name => {
    [
        const option = document.createElement("option");
      "All",
        option.value = name;
      "Wolf's Den","Wolf's Den II","Wolf's Den III",
        option.textContent = name;
      "The Red Nest","For The Eggs",
        dungeonSelect.appendChild(option);
      "Scarlet Revenge","Scarlet Revenge II",
      });
      "Promised One","Promised One II","Promised One III",
       "Lost and Found","The Hostage","Cactus On Fire",
      "Deep Echoes","Deep Echoes II","Deep Echoes III",
      "King's Hoard","King's Hoard II",
      "Hall of Daggers"
    ].forEach(name => {
      const opt = document.createElement("option");
      opt.value = name;
      opt.textContent = name;
      dungeonSelect.appendChild(opt);
    });
 
    filterWrapper.appendChild(document.createTextNode("Dungeon: "));
    filterWrapper.appendChild(dungeonSelect);


      wrapper.appendChild(dungeonSelect);
    const playerInput = document.createElement("input");
    playerInput.placeholder = "Search player...";
    playerInput.style.marginLeft = "10px";
    filterWrapper.appendChild(playerInput);


      const playerLabel = document.createElement("strong");
      playerLabel.innerText = "Player: ";
      wrapper.appendChild(playerLabel);


      const playerInput = document.createElement("input");
    /* ===========================
      playerInput.type = "text";
      SORT + RANK
      playerInput.placeholder = "Search player name...";
      =========================== */
      playerInput.style.padding = "4px";
      wrapper.appendChild(playerInput);


      container.appendChild(wrapper);
    function sortAndRank() {


      const rows = Array.from(table.querySelectorAll("tr")).slice(1);


       function sortAndRank() {
       rows.sort((a,b) => {


         const rows = Array.from(table.querySelectorAll("tr")).slice(1);
         if (a.cells.length < 5 || b.cells.length < 5) return 0;


         rows.sort((a,b) => {
         const dA = a.cells[1].innerText.trim();
        const dB = b.cells[1].innerText.trim();


          const dungeonA = a.cells[1].innerText.trim();
        if (dA !== dB) return dA.localeCompare(dB);
          const dungeonB = b.cells[1].innerText.trim();


          if (dungeonA < dungeonB) return -1;
        return parseTime(a.cells[4].innerText) - parseTime(b.cells[4].innerText);
          if (dungeonA > dungeonB) return 1;


          return parseTime(a.cells[4].innerText) - parseTime(b.cells[4].innerText);
      });


        });
      rows.forEach(r => table.appendChild(r));


        rows.forEach(row => table.appendChild(row));
      let currentDungeon = "";
      let position = 0;
      let rank = 0;
      let prevTime = null;
 
      rows.forEach(row => {


        if (row.cells.length < 5) return;


         let currentDungeon = "";
         const dungeon = row.cells[1].innerText.trim();
         let dungeonRank = 0;
         const time = parseTime(row.cells[4].innerText);
        let prevTime = null;
        let position = 0;


         rows.forEach(row => {
         if (dungeon !== currentDungeon) {
          currentDungeon = dungeon;
          position = 1;
          rank = 1;
          prevTime = time;
        } else {
          position++;
          if (time !== prevTime) rank = position;
          prevTime = time;
        }


          const dungeon = row.cells[1].innerText.trim();
        row.cells[0].innerText = rank;
          const time = parseTime(row.cells[4].innerText);
          const rankCell = row.cells[0];


          if (dungeon !== currentDungeon) {
      });
            currentDungeon = dungeon;
    }
            dungeonRank = 1;
            position = 1;
            prevTime = time;
          } else {
            position++;


            if (time !== prevTime) {
              dungeonRank = position;
            }


            prevTime = time;
    /* ===========================
          }
      BUILD TOP SECTION
      =========================== */


          // ✅ NUMBERS ONLY (no medals here)
    function buildTopSection() {
          rankCell.innerText = dungeonRank;


        });
      const rows = Array.from(table.querySelectorAll("tr")).slice(1);


       }
       const playerPoints = {};
      const records = {};


      rows.forEach(row => {


      /* ===========================
        if (row.cells.length < 5) return;
        FIXED COMPETITION LEADERBOARD
        =========================== */


      function buildCompetitionLeaderboard() {
        const dungeon = row.cells[1].innerText.trim();
        const player = row.cells[2].innerText.trim();
        const time = row.cells[4].innerText.trim();
        const rank = parseInt(row.cells[0].innerText);


         const rows = Array.from(table.querySelectorAll("tr")).slice(1);
         // Only require valid time (CRITICAL FIX)
        if (!time || !time.includes(":")) return;


         const playerPoints = {};
         /* ===========================
          POINTS
          =========================== */


         function getPoints(rank) {
         if (!isNaN(rank)) {
           const scoring = {
           playerPoints[player] = (playerPoints[player] || 0) + getPoints(rank);
            1: 25, 2: 20, 3: 17, 4: 15, 5: 13,
            6: 11, 7: 10, 8: 9, 9: 8, 10: 7,
            11: 6, 12: 5, 13: 4, 14: 3, 15: 2
          };
          return scoring[rank] || 1;
         }
         }


         rows.forEach(row => {
         /* ===========================
          WORLD RECORDS (WORKING)
          =========================== */


          const rank = parseInt(row.cells[0].innerText);
        const seconds = parseTime(time);
          const player = row.cells[2].innerText.trim();


           if (!player || isNaN(rank)) return;
        if (!records[dungeon]) {
          records[dungeon] = {
            time: time,
            seconds: seconds,
            players: [player]
          };
        } else {
           if (seconds < records[dungeon].seconds) {
            records[dungeon] = {
              time: time,
              seconds: seconds,
              players: [player]
            };
          } else if (seconds === records[dungeon].seconds) {
            records[dungeon].players.push(player);
          }
        }


          const points = getPoints(rank);
      });


          playerPoints[player] = (playerPoints[player] || 0) + points;


        });
      /* REMOVE OLD */
      const existing = document.getElementById("topSection");
      if (existing) existing.remove();


        const leaderboard = Object.entries(playerPoints)
          .map(([player, points]) => ({ player, points }))
          .sort((a, b) => b.points - a.points);


        const existing = document.getElementById("competitionLeaderboard");
      /* FLEX CONTAINER */
        if (existing) existing.remove();
      const top = document.createElement("div");
      top.id = "topSection";
      top.style.display = "flex";
      top.style.gap = "30px";
      top.style.marginBottom = "25px";


        const container = document.createElement("div");
        container.id = "competitionLeaderboard";
        container.style.marginBottom = "20px";


        const title = document.createElement("h2");
      /* ===========================
        title.innerText = "🏆 Competition Leaderboard";
        COMPETITION TABLE
        container.appendChild(title);
        =========================== */


        const tableEl = document.createElement("table");
      const compBox = document.createElement("div");
        tableEl.className = "wikitable";
      compBox.style.flex = "1";
        tableEl.style.width = "50%";


        tableEl.innerHTML = `
      const compTitle = document.createElement("h2");
          <tr>
      compTitle.innerText = "🏆 Competition Leaderboard";
            <th>Rank</th>
      compBox.appendChild(compTitle);
            <th>Player</th>
            <th>Points</th>
          </tr>
        `;


        leaderboard.forEach((entry, index) => {
      const compTable = document.createElement("table");
      compTable.className = "wikitable";
      compTable.style.width = "100%";


           const medals = ["🥇", "🥈", "🥉"];
      compTable.innerHTML = `
        <tr>
          <th>Rank</th>
          <th>Player</th>
           <th>Points</th>
        </tr>
      `;


          const row = document.createElement("tr");
      const leaderboard = Object.entries(playerPoints)
        .map(([player, points]) => ({player, points}))
        .sort((a,b)=>b.points-a.points);


          row.innerHTML = `
      let prevPoints = null;
            <td>${medals[index] || (index + 1)}</td>
      let rank = 0;
            <td>${entry.player}</td>
      let position = 0;
            <td>${entry.points}</td>
          `;


          tableEl.appendChild(row);
      leaderboard.forEach(entry => {


         });
         position++;
        if (entry.points !== prevPoints) rank = position;
        prevPoints = entry.points;


         container.appendChild(tableEl);
         const medal =
        table.parentNode.insertBefore(container, table);
          rank===1?"🥇 1":
          rank===2?"🥈 2":
          rank===3?"🥉 3":
          rank;


      }
        const row = document.createElement("tr");
        row.innerHTML = `
          <td>${medal}</td>
          <td>${entry.player}</td>
          <td>${entry.points}</td>
        `;
        compTable.appendChild(row);


      });


       function filterTable() {
       compBox.appendChild(compTable);


        const selectedDungeon = dungeonSelect.value;
        const playerSearch = playerInput.value.toLowerCase();


        const rows = Array.from(table.querySelectorAll("tr")).slice(1);
      /* ===========================
        WORLD RECORD TABLE
        =========================== */


        rows.forEach(row => {
      const wrBox = document.createElement("div");
      wrBox.style.flex = "1";


          const dungeon = row.cells[1].innerText.trim();
      const wrTitle = document.createElement("h2");
          const player = row.cells[2].innerText.toLowerCase();
      wrTitle.innerText = "🏁 Competition Dungeon Records";
      wrBox.appendChild(wrTitle);


          row.style.display =
      const wrTable = document.createElement("table");
            (selectedDungeon === "All" || dungeon === selectedDungeon) &&
      wrTable.className = "wikitable";
            player.includes(playerSearch)
      wrTable.style.width = "100%";
              ? ""
              : "none";


         });
      wrTable.innerHTML = `
         <tr>
          <th>Dungeon</th>
          <th>Time</th>
          <th>Player(s)</th>
        </tr>
      `;


       }
       Object.entries(records).forEach(([d, data]) => {


      dungeonSelect.addEventListener("change", filterTable);
        const row = document.createElement("tr");
      playerInput.addEventListener("input", filterTable);


      sortAndRank();
        row.innerHTML = `
      buildCompetitionLeaderboard(); // ✅ runs once only
          <td>${d}</td>
    }
          <td>${data.time}</td>
  }
          <td>${data.players.join(", ")}</td>
        `;


        wrTable.appendChild(row);


  /* ===========================
      });
    PARTY TABLE (unchanged)
    =========================== */


  const partyTable = document.getElementById("partyLeaderboardTable");
      wrBox.appendChild(wrTable);


  if (partyTable) {


    function sortAndRankParty() {
      /* COMBINE */
      top.appendChild(compBox);
      top.appendChild(wrBox);


       const rows = Array.from(partyTable.querySelectorAll("tr")).slice(1);
       table.parentNode.insertBefore(top, table);


       rows.sort((a,b) => {
       /* FILTER BELOW */
 
      table.parentNode.insertBefore(filterWrapper, table);
        const dungeonA = a.cells[1].innerText.trim();
    }
        const dungeonB = b.cells[1].innerText.trim();


        if (dungeonA < dungeonB) return -1;
        if (dungeonA > dungeonB) return 1;


        return parseTime(a.cells[5].innerText) - parseTime(b.cells[5].innerText);
    /* ===========================
      FILTER
      =========================== */


      });
    function filterTable() {


       rows.forEach(row => partyTable.appendChild(row));
       const dungeon = dungeonSelect.value;
      const player = playerInput.value.toLowerCase();


       let currentDungeon = "";
       const rows = Array.from(table.querySelectorAll("tr")).slice(1);
      let dungeonRank = 0;
      let prevTime = null;
      let position = 0;


       rows.forEach(row => {
       rows.forEach(row => {


         const dungeon = row.cells[1].innerText.trim();
         if (row.cells.length < 5) return;
        const time = parseTime(row.cells[5].innerText);
        const rankCell = row.cells[0];


         if (dungeon !== currentDungeon) {
         const d = row.cells[1].innerText.trim();
          currentDungeon = dungeon;
        const p = row.cells[2].innerText.toLowerCase();
          dungeonRank = 1;
          position = 1;
          prevTime = time;
        } else {
          position++;


           if (time !== prevTime) {
        row.style.display =
             dungeonRank = position;
           (dungeon === "All" || d === dungeon) &&
          }
          p.includes(player)
            ? ""
             : "none";
      });
    }


          prevTime = time;
    dungeonSelect.addEventListener("change", filterTable);
        }
    playerInput.addEventListener("input", filterTable);


        rankCell.innerText = dungeonRank;


      });
    /* ===========================
      INIT
      =========================== */


     }
     sortAndRank();
    buildTopSection();


    sortAndRankParty();
  });
  }


});
});