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
Tag: Reverted
No edit summary
 
(47 intermediate revisions by the same user not shown)
Line 1: Line 1:
/* Any JavaScript here will be loaded for all users on every page load. */
/* Any JavaScript here will be loaded for all users on every page load. */


(function(){
mw.hook('wikipage.content').add(function ($content) {
   // --- Leaflet CSS/JS ---
   $content.find('.spoiler').on('click', function () {
  var LCSS='https://unpkg.com/leaflet@1.9.4/dist/leaflet.css';
    $(this).toggleClass('revealed');
   var LJS ='https://unpkg.com/leaflet@1.9.4/dist/leaflet.js';
   });
   if (!document.querySelector('link[href="'+LCSS+'"]')) {
});
     var l=document.createElement('link'); l.rel='stylesheet'; l.href=LCSS; document.head.appendChild(l);
 
 
mw.hook('wikipage.content').add(function () {
 
  /* ===========================
    WAIT FOR TABLE (IMPORTANT)
    =========================== */
 
   function waitForTable(callback) {
    const table = document.getElementById("leaderboardTable");
    if (table) {
      callback(table);
     } else {
      setTimeout(() => waitForTable(callback), 50);
    }
   }
   }
  function loadScript(src, cb){ var s=document.createElement('script'); s.src=src; s.onload=cb; document.head.appendChild(s); }
  function fileUrl(title){ return new URL(mw.util.getUrl('Special:FilePath/'+title), location.origin).href; }


  // --- CSS for emoji markers ---
  (function(){
    var css = `
      .tv-icon{display:flex;align-items:center;justify-content:center;
        width:30px;height:30px;border-radius:50%;box-shadow:0 0 0 2px #fff, 0 2px 6px rgba(0,0,0,.25);
        font-size:18px;line-height:1;}
      .tv-icon-boss{ background:#c0392b; }    /* red */
      .tv-icon-pebble{ background:#7f8c8d; }  /* gray */
      .tv-icon-logs{ background:#8e5a2b; }    /* brown */
      .tv-icon span{ transform:translateY(1px); }
    `;
    var st=document.createElement('style'); st.textContent=css; document.head.appendChild(st);
  })();


   // === CONFIG ===
   waitForTable(function(table) {
  var MAP_IMAGE_FILE = 'TreeverseMap.png';   // exact filename of your uploaded map
 
  // ==============
    /* ===========================
      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;
    }
 
 
    /* ===========================
      FILTER UI
      =========================== */
 
    const filterWrapper = document.createElement("div");
    filterWrapper.style.marginBottom = "20px";
 
    const filterTitle = document.createElement("h3");
    filterTitle.innerText = "🔍 Filter Runs";
    filterWrapper.appendChild(filterTitle);
 
    const dungeonSelect = document.createElement("select");
    dungeonSelect.style.marginRight = "10px";


  // Make a divIcon with emoji
     [
  function makeEmojiIcon(kind){
      "All",
     var cls = 'tv-icon ';
      "Wolf's Den","Wolf's Den II","Wolf's Den III",
    var emoji = '❓';
      "The Red Nest","For The Eggs",
    if (kind==='boss'){ cls+='tv-icon-boss'; emoji='👹'; }
      "Scarlet Revenge","Scarlet Revenge II",
    else if (kind==='pebble'){ cls+='tv-icon-pebble'; emoji='🪨'; }
       "Promised One","Promised One II","Promised One III",
    else if (kind==='logs'){ cls+='tv-icon-logs'; emoji='🪓'; }
       "Lost and Found","The Hostage","Cactus On Fire",
    return L.divIcon({
       "Deep Echoes","Deep Echoes II","Deep Echoes III",
       className: cls,
       "King's Hoard","King's Hoard II",
       html: '<span>'+emoji+'</span>',
       "Hall of Daggers"
       iconSize: [30,30],
    ].forEach(name => {
       iconAnchor: [15,30],
      const opt = document.createElement("option");
       popupAnchor: [0,-24]
      opt.value = name;
      opt.textContent = name;
      dungeonSelect.appendChild(opt);
     });
     });
  }


  // Control for adding markers
    filterWrapper.appendChild(document.createTextNode("Dungeon: "));
  function makeControl(map, layers){
    filterWrapper.appendChild(dungeonSelect);
     var c=L.control({position:'topright'});
 
     c.onAdd=function(){
     const playerInput = document.createElement("input");
       var d=L.DomUtil.create('div','leaflet-bar');
     playerInput.placeholder = "Search player...";
       d.style.padding='8px'; d.style.background='#fff'; d.style.minWidth='220px'; d.style.font='14px/1.2 sans-serif';
    playerInput.style.marginLeft = "10px";
      d.innerHTML=''
    filterWrapper.appendChild(playerInput);
         +'<div style="font-weight:600;margin-bottom:6px">Add marker</div>'
 
        +'<label style="display:block;margin:4px 0 2px">Type</label>'
 
        +'<select id="tm-type" style="width:100%">'
    /* ===========================
        +'<option value="boss">Boss 👹</option>'
      SORT + RANK
        +'<option value="pebble">Pebble 🪨</option>'
      =========================== */
        +'<option value="logs">Logs 🪓</option>'
 
         +'</select>'
    function sortAndRank() {
         +'<label style="display:block;margin:6px 0 2px">Title</label>'
 
         +'<input id="tm-title" placeholder="Name…" style="width:100%;box-sizing:border-box">'
       const rows = Array.from(table.querySelectorAll("tr")).slice(1);
         +'<label style="display:block;margin:6px 0 2px">Coords (y, x)</label>'
 
         +'<input id="tm-y" placeholder="y" style="width:48%">'
      rows.sort((a,b) => {
        +'<input id="tm-x" placeholder="x" style="width:48%;float:right">'
 
        +'<div style="clear:both;margin-top:8px"></div>'
        if (a.cells.length < 5 || b.cells.length < 5) return 0;
         +'<button id="tm-add" class="mw-ui-button" style="width:100%">Add</button>';
 
       L.DomEvent.disableClickPropagation(d);
        const dA = a.cells[1].innerText.trim();
        const dB = b.cells[1].innerText.trim();
 
        if (dA !== dB) return dA.localeCompare(dB);
 
        return parseTime(a.cells[4].innerText) - parseTime(b.cells[4].innerText);
 
      });
 
       rows.forEach(r => table.appendChild(r));
 
      let currentDungeon = "";
      let position = 0;
      let rank = 0;
      let prevTime = null;
 
      rows.forEach(row => {
 
        if (row.cells.length < 5) return;
 
        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;
        }
 
        row.cells[0].innerText = rank;
 
      });
    }
 
 
    /* ===========================
      BUILD TOP SECTION
      =========================== */
 
    function buildTopSection() {
 
      const rows = Array.from(table.querySelectorAll("tr")).slice(1);
 
      const playerPoints = {};
      const records = {};
 
      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);
 
        // Only require valid time (CRITICAL FIX)
        if (!time || !time.includes(":")) return;
 
        /* ===========================
          POINTS
          =========================== */
 
         if (!isNaN(rank)) {
          playerPoints[player] = (playerPoints[player] || 0) + getPoints(rank);
        }
 
        /* ===========================
          WORLD RECORDS (WORKING)
          =========================== */
 
        const seconds = parseTime(time);
 
        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);
          }
        }
 
      });
 
 
      /* REMOVE OLD */
      const existing = document.getElementById("topSection");
      if (existing) existing.remove();
 
 
      /* FLEX CONTAINER */
      const top = document.createElement("div");
      top.id = "topSection";
      top.style.display = "flex";
      top.style.gap = "30px";
      top.style.marginBottom = "25px";
 
 
      /* ===========================
        COMPETITION TABLE
        =========================== */
 
      const compBox = document.createElement("div");
      compBox.style.flex = "1";
 
      const compTitle = document.createElement("h2");
      compTitle.innerText = "🏆 Competition Leaderboard";
      compBox.appendChild(compTitle);
 
      const compTable = document.createElement("table");
      compTable.className = "wikitable";
      compTable.style.width = "100%";
 
      compTable.innerHTML = `
        <tr>
          <th>Rank</th>
          <th>Player</th>
          <th>Points</th>
         </tr>
      `;
 
      const leaderboard = Object.entries(playerPoints)
         .map(([player, points]) => ({player, points}))
        .sort((a,b)=>b.points-a.points);
 
      let prevPoints = null;
      let rank = 0;
      let position = 0;
 
      leaderboard.forEach(entry => {
 
         position++;
        if (entry.points !== prevPoints) rank = position;
        prevPoints = entry.points;
 
        const medal =
          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);
 
      });
 
      compBox.appendChild(compTable);
 
 
      /* ===========================
        WORLD RECORD TABLE
        =========================== */
 
      const wrBox = document.createElement("div");
      wrBox.style.flex = "1";
 
      const wrTitle = document.createElement("h2");
      wrTitle.innerText = "🏁 Competition Dungeon Records";
      wrBox.appendChild(wrTitle);
 
      const wrTable = document.createElement("table");
      wrTable.className = "wikitable";
      wrTable.style.width = "100%";
 
      wrTable.innerHTML = `
        <tr>
          <th>Dungeon</th>
          <th>Time</th>
          <th>Player(s)</th>
         </tr>
      `;
 
      Object.entries(records).forEach(([d, data]) => {
 
        const row = document.createElement("tr");
 
        row.innerHTML = `
          <td>${d}</td>
          <td>${data.time}</td>
          <td>${data.players.join(", ")}</td>
        `;
 
        wrTable.appendChild(row);
 
      });
 
      wrBox.appendChild(wrTable);
 
 
      /* COMBINE */
      top.appendChild(compBox);
       top.appendChild(wrBox);
 
      table.parentNode.insertBefore(top, table);
 
      /* FILTER BELOW */
      table.parentNode.insertBefore(filterWrapper, table);
    }
 
 
    /* ===========================
      FILTER
      =========================== */
 
    function filterTable() {
 
      const dungeon = dungeonSelect.value;
      const player = playerInput.value.toLowerCase();
 
      const rows = Array.from(table.querySelectorAll("tr")).slice(1);
 
      rows.forEach(row => {
 
        if (row.cells.length < 5) return;


      var addMarker=function(){};
         const d = row.cells[1].innerText.trim();
      d.querySelector('#tm-add').onclick=function(){
         const p = row.cells[2].innerText.toLowerCase();
         var t=d.querySelector('#tm-type').value,
            title=(d.querySelector('#tm-title').value||'').trim(),
            y=parseFloat(d.querySelector('#tm-y').value),
            x=parseFloat(d.querySelector('#tm-x').value);
        if(isNaN(y)||isNaN(x)){alert('Enter numbers for y and x');return;}
        addMarker(t,[y,x],title||t);
      };
      c.setAddMarker=function(fn){ addMarker=fn; };
      c.setInputs=function(latlng){
        d.querySelector('#tm-y').value=Math.round(latlng.lat);
         d.querySelector('#tm-x').value=Math.round(latlng.lng);
      };
      return d;
    };
    c.addTo(map); return c;
  }


  function init(){
        row.style.display =
    var img=new Image(), mapImgUrl=fileUrl(MAP_IMAGE_FILE);
          (dungeon === "All" || d === dungeon) &&
    img.onload=function(){
          p.includes(player)
      var w=img.naturalWidth, h=img.naturalHeight;
            ? ""
      if(!w||!h){ alert('Could not read size for '+MAP_IMAGE_FILE); return; }
            : "none";
      var el=document.getElementById('treeverse-map'); if(!el) return;
       });
      var map=L.map(el,{crs:L.CRS.Simple,minZoom:-2,maxZoom:5});
    }
       var bounds=[[0,0],[h,w]];
      L.imageOverlay(mapImgUrl,bounds).addTo(map);
      map.fitBounds(bounds);


      var layers={ boss:L.layerGroup().addTo(map),
    dungeonSelect.addEventListener("change", filterTable);
                  pebble:L.layerGroup().addTo(map),
    playerInput.addEventListener("input", filterTable);
                  logs:L.layerGroup().addTo(map) };
      var icons={ boss:makeEmojiIcon('boss'),
                  pebble:makeEmojiIcon('pebble'),
                  logs:makeEmojiIcon('logs') };


      function addMarker(type,latlng,title){
        var m=L.marker(latlng,{icon:icons[type],title:title||type}).addTo(layers[type]);
        m.bindPopup('<b>'+ (title||type) +'</b><br>(y:'+Math.round(latlng[0])+', x:'+Math.round(latlng[1])+')');
        return m;
      }


      var control=makeControl(map,layers);
    /* ===========================
      control.setAddMarker(addMarker);
      INIT
      map.on('click',e=>control.setInputs(e.latlng));
      =========================== */


      L.control.layers(null,{
    sortAndRank();
        'Bosses 👹':layers.boss,
    buildTopSection();
        'Pebbles 🪨':layers.pebble,
        'Logs 🪓':layers.logs
      },{collapsed:false}).addTo(map);


      // Example starter markers
  });
      addMarker('town',[Math.round(h*0.55),Math.round(w*0.45)],'Spawn'); // optional
      addMarker('pebble',[Math.round(h*0.40),Math.round(w*0.70)],'Pebble Patch');
      addMarker('boss',[Math.round(h*0.25),Math.round(w*0.30)],'Ancient Demon');
      addMarker('logs',[Math.round(h*0.60),Math.round(w*0.60)],'Logging Camp');
    };
    img.onerror=function(){ alert('Could not load map image "'+MAP_IMAGE_FILE+'". Check Special:ListFiles.'); };
    img.src=mapImgUrl+'?v='+Date.now();
  }


  loadScript(LJS, init);
});
})();

Latest revision as of 18:07, 14 May 2026

/* Any JavaScript here will be loaded for all users on every page load. */

mw.hook('wikipage.content').add(function ($content) {
  $content.find('.spoiler').on('click', function () {
    $(this).toggleClass('revealed');
  });
});


mw.hook('wikipage.content').add(function () {

  /* ===========================
     WAIT FOR TABLE (IMPORTANT)
     =========================== */

  function waitForTable(callback) {
    const table = document.getElementById("leaderboardTable");
    if (table) {
      callback(table);
    } else {
      setTimeout(() => waitForTable(callback), 50);
    }
  }


  waitForTable(function(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;
    }


    /* ===========================
       FILTER UI
       =========================== */

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

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

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

    [
      "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);
    });

    filterWrapper.appendChild(document.createTextNode("Dungeon: "));
    filterWrapper.appendChild(dungeonSelect);

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


    /* ===========================
       SORT + RANK
       =========================== */

    function sortAndRank() {

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

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

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

        if (dA !== dB) return dA.localeCompare(dB);

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

      });

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

      let currentDungeon = "";
      let position = 0;
      let rank = 0;
      let prevTime = null;

      rows.forEach(row => {

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

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

        row.cells[0].innerText = rank;

      });
    }


    /* ===========================
       BUILD TOP SECTION
       =========================== */

    function buildTopSection() {

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

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

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

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

        /* ===========================
           POINTS
           =========================== */

        if (!isNaN(rank)) {
          playerPoints[player] = (playerPoints[player] || 0) + getPoints(rank);
        }

        /* ===========================
           WORLD RECORDS (WORKING)
           =========================== */

        const seconds = parseTime(time);

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

      });


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


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


      /* ===========================
         COMPETITION TABLE
         =========================== */

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

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

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

      compTable.innerHTML = `
        <tr>
          <th>Rank</th>
          <th>Player</th>
          <th>Points</th>
        </tr>
      `;

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

      let prevPoints = null;
      let rank = 0;
      let position = 0;

      leaderboard.forEach(entry => {

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

        const medal =
          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);

      });

      compBox.appendChild(compTable);


      /* ===========================
         WORLD RECORD TABLE
         =========================== */

      const wrBox = document.createElement("div");
      wrBox.style.flex = "1";

      const wrTitle = document.createElement("h2");
      wrTitle.innerText = "🏁 Competition Dungeon Records";
      wrBox.appendChild(wrTitle);

      const wrTable = document.createElement("table");
      wrTable.className = "wikitable";
      wrTable.style.width = "100%";

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

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

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

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

        wrTable.appendChild(row);

      });

      wrBox.appendChild(wrTable);


      /* COMBINE */
      top.appendChild(compBox);
      top.appendChild(wrBox);

      table.parentNode.insertBefore(top, table);

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


    /* ===========================
       FILTER
       =========================== */

    function filterTable() {

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

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

      rows.forEach(row => {

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

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

        row.style.display =
          (dungeon === "All" || d === dungeon) &&
          p.includes(player)
            ? ""
            : "none";
      });
    }

    dungeonSelect.addEventListener("change", filterTable);
    playerInput.addEventListener("input", filterTable);


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

    sortAndRank();
    buildTopSection();

  });

});