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
 
(50 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(){
 
   // --- Leaflet CSS/JS ---
mw.hook('wikipage.content').add(function ($content) {
  var LCSS='https://unpkg.com/leaflet@1.9.4/dist/leaflet.css';
   $content.find('.spoiler').on('click', function () {
   var LJS ='https://unpkg.com/leaflet@1.9.4/dist/leaflet.js';
    $(this).toggleClass('revealed');
   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.jpg';   // 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);


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