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
 
(12 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
 
    =========================== */
    /* ===========================
      HELPERS
      =========================== */
 
    function parseTime(t) {
      if (!t || !t.includes(":")) return 999999;
      const [m, s] = t.trim().split(":").map(Number);
      return m * 60 + s;
    }
 
    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;
    }
 


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


  if (table) {
    const filterWrapper = document.createElement("div");
    filterWrapper.style.marginBottom = "20px";


     const container = document.getElementById("filterContainer");
     const filterTitle = document.createElement("h3");
    filterTitle.innerText = "🔍 Filter Runs";
    filterWrapper.appendChild(filterTitle);


     if (container) {
     const dungeonSelect = document.createElement("select");
    dungeonSelect.style.marginRight = "10px";


       const wrapper = document.createElement("div");
    [
       wrapper.style.marginBottom = "15px";
      "All",
      "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"
    ].forEach(name => {
       const opt = document.createElement("option");
       opt.value = name;
      opt.textContent = name;
      dungeonSelect.appendChild(opt);
    });


      const dungeonLabel = document.createElement("strong");
    filterWrapper.appendChild(document.createTextNode("Dungeon: "));
      dungeonLabel.innerText = "Dungeon: ";
    filterWrapper.appendChild(dungeonSelect);
      wrapper.appendChild(dungeonLabel);


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


      const dungeons = [
        "All",
        "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");
      SORT + RANK
        option.value = name;
      =========================== */
        option.textContent = name;
        dungeonSelect.appendChild(option);
      });


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


       const playerLabel = document.createElement("strong");
       const rows = Array.from(table.querySelectorAll("tr")).slice(1);
      playerLabel.innerText = "Player: ";
      wrapper.appendChild(playerLabel);


       const playerInput = document.createElement("input");
       rows.sort((a,b) => {
      playerInput.type = "text";
      playerInput.placeholder = "Search player name...";
      playerInput.style.padding = "4px";
      wrapper.appendChild(playerInput);


      container.appendChild(wrapper);
        if (a.cells.length < 5 || b.cells.length < 5) return 0;


        const dA = a.cells[1].innerText.trim();
        const dB = b.cells[1].innerText.trim();


      function sortAndRank() {
        if (dA !== dB) return dA.localeCompare(dB);


         const rows = Array.from(table.querySelectorAll("tr")).slice(1);
         return parseTime(a.cells[4].innerText) - parseTime(b.cells[4].innerText);


        rows.sort((a,b) => {
      });


          const dungeonA = a.cells[1].innerText.trim();
      rows.forEach(r => table.appendChild(r));
          const dungeonB = b.cells[1].innerText.trim();


          if (dungeonA < dungeonB) return -1;
      let currentDungeon = "";
          if (dungeonA > dungeonB) return 1;
      let position = 0;
      let rank = 0;
      let prevTime = null;


          return parseTime(a.cells[4].innerText) - parseTime(b.cells[4].innerText);
      rows.forEach(row => {


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


         rows.forEach(row => table.appendChild(row));
         const dungeon = row.cells[1].innerText.trim();
        const time = parseTime(row.cells[4].innerText);


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


         let currentDungeon = "";
         row.cells[0].innerText = rank;
        let dungeonRank = 0;
        let prevTime = null;
        let position = 0;


        rows.forEach(row => {
      });
    }


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


          if (dungeon !== currentDungeon) {
    /* ===========================
            currentDungeon = dungeon;
      BUILD TOP SECTION
            dungeonRank = 1;
      =========================== */
            position = 1;
            prevTime = time;
          } else {
            position++;


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


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


          if (dungeonRank === 1) rankCell.innerHTML = "🥇 1";
      const playerPoints = {};
          else if (dungeonRank === 2) rankCell.innerHTML = "🥈 2";
      const records = {};
          else if (dungeonRank === 3) rankCell.innerHTML = "🥉 3";
          else rankCell.innerText = dungeonRank;


        });
      rows.forEach(row => {


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


        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);


      function filterTable() {
        // Only require valid time (CRITICAL FIX)
        if (!time || !time.includes(":")) return;


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


         const rows = Array.from(table.querySelectorAll("tr")).slice(1);
         if (!isNaN(rank)) {
          playerPoints[player] = (playerPoints[player] || 0) + getPoints(rank);
        }


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


          const dungeon = row.cells[1].innerText.trim();
        const seconds = parseTime(time);
          const player = row.cells[2].innerText.toLowerCase();


           row.style.display =
        if (!records[dungeon]) {
             (selectedDungeon === "All" || dungeon === selectedDungeon) &&
          records[dungeon] = {
             player.includes(playerSearch)
            time: time,
              ? ""
            seconds: seconds,
              : "none";
            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);
          }
        }


        });
      });


      }


       dungeonSelect.addEventListener("change", filterTable);
       /* REMOVE OLD */
       playerInput.addEventListener("input", filterTable);
      const existing = document.getElementById("topSection");
       if (existing) existing.remove();


      sortAndRank();
    }
  }


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


  /* ===========================
    PARTY TABLE
    =========================== */


  const partyTable = document.getElementById("partyLeaderboardTable");
      /* ===========================
        COMPETITION TABLE
        =========================== */


  if (partyTable) {
      const compBox = document.createElement("div");
      compBox.style.flex = "1";


    const partyContainer = document.getElementById("partyFilterContainer");
      const compTitle = document.createElement("h2");
      compTitle.innerText = "🏆 Competition Leaderboard";
      compBox.appendChild(compTitle);


    if (partyContainer) {
      const compTable = document.createElement("table");
      compTable.className = "wikitable";
      compTable.style.width = "100%";


       const wrapper = document.createElement("div");
       compTable.innerHTML = `
       wrapper.style.marginBottom = "15px";
        <tr>
          <th>Rank</th>
          <th>Player</th>
          <th>Points</th>
        </tr>
       `;


       const dungeonLabel = document.createElement("strong");
       const leaderboard = Object.entries(playerPoints)
      dungeonLabel.innerText = "Dungeon: ";
        .map(([player, points]) => ({player, points}))
      wrapper.appendChild(dungeonLabel);
        .sort((a,b)=>b.points-a.points);


       const dungeonSelect = document.createElement("select");
       let prevPoints = null;
       dungeonSelect.style.marginRight = "20px";
       let rank = 0;
      let position = 0;


       const dungeons = [
       leaderboard.forEach(entry => {
        "All",
        "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 => {
         position++;
         const option = document.createElement("option");
         if (entry.points !== prevPoints) rank = position;
         option.value = name;
         prevPoints = entry.points;
         option.textContent = name;
        dungeonSelect.appendChild(option);
      });


      wrapper.appendChild(dungeonSelect);
        const medal =
          rank===1?"🥇 1":
          rank===2?"🥈 2":
          rank===3?"🥉 3":
          rank;


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


       const playerInput = document.createElement("input");
       });
      playerInput.type = "text";
      playerInput.placeholder = "Search player name...";
      playerInput.style.padding = "4px";
      wrapper.appendChild(playerInput);


       partyContainer.appendChild(wrapper);
       compBox.appendChild(compTable);




       function sortAndRankParty() {
       /* ===========================
        WORLD RECORD TABLE
        =========================== */


        const rows = Array.from(partyTable.querySelectorAll("tr")).slice(1);
      const wrBox = document.createElement("div");
      wrBox.style.flex = "1";


        rows.sort((a,b) => {
      const wrTitle = document.createElement("h2");
      wrTitle.innerText = "🏁 Competition Dungeon Records";
      wrBox.appendChild(wrTitle);


          const dungeonA = a.cells[1].innerText.trim();
      const wrTable = document.createElement("table");
          const dungeonB = b.cells[1].innerText.trim();
      wrTable.className = "wikitable";
      wrTable.style.width = "100%";


           if (dungeonA < dungeonB) return -1;
      wrTable.innerHTML = `
           if (dungeonA > dungeonB) return 1;
        <tr>
          <th>Dungeon</th>
           <th>Time</th>
           <th>Player(s)</th>
        </tr>
      `;


          return parseTime(a.cells[5].innerText) - parseTime(b.cells[5].innerText);
      Object.entries(records).forEach(([d, data]) => {


         });
         const row = document.createElement("tr");


         rows.forEach(row => partyTable.appendChild(row));
         row.innerHTML = `
          <td>${d}</td>
          <td>${data.time}</td>
          <td>${data.players.join(", ")}</td>
        `;


        wrTable.appendChild(row);


        let currentDungeon = "";
      });
        let dungeonRank = 0;
        let prevTime = null;
        let position = 0;


        rows.forEach(row => {
      wrBox.appendChild(wrTable);


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


          if (dungeon !== currentDungeon) {
      /* COMBINE */
            currentDungeon = dungeon;
      top.appendChild(compBox);
            dungeonRank = 1;
      top.appendChild(wrBox);
            position = 1;
            prevTime = time;
          } else {
            position++;


            if (time !== prevTime) {
      table.parentNode.insertBefore(top, table);
              dungeonRank = position;
            }


            prevTime = time;
      /* FILTER BELOW */
          }
      table.parentNode.insertBefore(filterWrapper, table);
    }


          if (dungeonRank === 1) rankCell.innerHTML = "🥇 1";
          else if (dungeonRank === 2) rankCell.innerHTML = "🥈 2";
          else if (dungeonRank === 3) rankCell.innerHTML = "🥉 3";
          else rankCell.innerText = dungeonRank;


        });
    /* ===========================
      FILTER
      =========================== */


      }
    function filterTable() {


      const dungeon = dungeonSelect.value;
      const player = playerInput.value.toLowerCase();


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


        const selectedDungeon = dungeonSelect.value;
      rows.forEach(row => {
        const playerSearch = playerInput.value.toLowerCase();


         const rows = Array.from(partyTable.querySelectorAll("tr")).slice(1);
         if (row.cells.length < 5) return;


         rows.forEach(row => {
         const d = row.cells[1].innerText.trim();
        const p = row.cells[2].innerText.toLowerCase();


          const dungeon = row.cells[1].innerText.trim();
        row.style.display =
           const player = row.cells[3].innerText.toLowerCase();
          (dungeon === "All" || d === dungeon) &&
           p.includes(player)
            ? ""
            : "none";
      });
    }


          row.style.display =
    dungeonSelect.addEventListener("change", filterTable);
            (selectedDungeon === "All" || dungeon === selectedDungeon) &&
    playerInput.addEventListener("input", filterTable);
            player.includes(playerSearch)
              ? ""
              : "none";


        });


      }
    /* ===========================
      INIT
      =========================== */


      dungeonSelect.addEventListener("change", filterPartyTable);
    sortAndRank();
      playerInput.addEventListener("input", filterPartyTable);
    buildTopSection();


      sortAndRankParty();
  });
    }
  }


});
});