This is a wiki for a reason. Anyone can contribute. If you see something that is inaccurate or can be improved, don't ask that it be fixed--just improve it.
[ Disclaimer, Create new user --- Wiki markup help, Install P99 ]

MediaWiki:LocMaps.js

From Project 1999 Wiki
Revision as of 21:18, 1 September 2019 by Loramin (Talk | contribs)

Jump to: navigation, search

Note: After saving, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Clear the cache in Tools → Preferences
// This first module powers the {{Loc}} template links.  It uses separate code from the module below
// (which powers the "loc maps" on NPC pages.

// TODO: Merge the two modules (mainly make the second use the first's code)

(function() { // module start

  // TODO: Add support for cropping maps that have multiple maps.
  // For instance, the following CSS styles show only one of the three maps for 
  // Erudin Palace:
  // background-image:url(/images/Erudinpalace.jpg); 
  // background-position: 250px 0px; width: 275px; height: 272px
  // Figure out how to make the Xs line up with those styles added

  var locIsWithinAlternateData = function(loc, alternateData) {
    return loc.x < alternateData.maxX &&
        loc.x > alternateData.minX &&
        loc.y < alternateData.maxY &&
        loc.y > alternateData.minY;
  };



  /**
   * Determines if the provided name contains any of the provided words
   */
  // TODO: Add a some polyfill to make this a lot simpler
  var containsAny = function(name/* word1, word2, etc. */) {
    for(var arg = 1; arg < arguments.length; ++ arg) {
      var word = arguments[arg];
      if (name.includes(word)) return true;
    }
    return false;
  };

  var containsAnyNumber = function(name, number, suffix) {
    return containsAny.call(null, name, 
           number + suffix,
           'level ' + number,
           'level: ' + number
    );
  };

  var getZoneLevelData = function(levels, text) {
    // Check for part "level" aliases (eg. "1st floor" vs. "Level One")
    text = text.toLowerCase();
    if (containsAnyNumber(text, 0, 'th') || containsAny(text, 'basement', 'underground')) return levels[0];
    if (containsAnyNumber(text, 1, 'st') || containsAny(text, 'one', 'first', 'ground')) return levels[1];
    if (containsAnyNumber(text, 2, 'nd') || containsAny(text, 'two', 'second')) return levels[2];
    if (containsAnyNumber(text, 3, 'rd') || containsAny(text, 'three', 'third')) return levels[3];
    if (containsAnyNumber(text, 4, 'th') || containsAny(text, 'four', 'fourth')) return levels[4];
    if (containsAnyNumber(text, 5, 'th') || containsAny(text, 'five', 'fifth')) return levels[5];
    if (containsAnyNumber(text, 6, 'th') || containsAny(text, 'six', 'sixth')) return levels[6];
    if (containsAnyNumber(text, 7, 'th') || containsAny(text, 'seven', 'seventh')) return levels[7];
    if (containsAnyNumber(text, 8, 'th') || containsAny(text, 'eight', 'eighth')) return levels[8];
    if (containsAnyNumber(text, 9, 'th') || containsAny(text, 'nine', 'ninth')) return levels[9];

    // If we still couldn't match, and there is a ground/1st floor, use it
    return levels[1];
  };

  var getZoneData = function(zoneName, locs, nonLocParts) {
    var zone = zoneData[zoneName];

    // Handle aliases (eg. "North Ro" instead of "Northern Desert of Ro")
    if (typeof(zone) === 'string') zone = zoneData[zone];

    // Handle Multi-Level Zones (with different maps for 1st, 2nd, etc. floor)
    if (zone.levels) return getZoneLevelData(zone.levels, nonLocParts);

    // Handle Zones with alternate maps on the same level (eg. Kelethin in GFay)
    if (!zone.alternateMaps) return zone;

    for (var i = 0; i < zone.alternateMaps.length; i++) {
      var alternateData = zone.alternateMaps[i];
      var allLocsAreWithin = true;
      // wish I had ES6 [].every
      $.each(locs, function(i, loc) {
        allLocsAreWithin = allLocsAreWithin && locIsWithinAlternateData(loc, alternateData);
      });
      if (allLocsAreWithin) return alternateData;
    };
    return zone;
  };

var findZoneData = function(zoneName, locs, nonLocParts) {
  var zone = zoneData[zoneName];

  // Handle aliases (eg. "North Ro" instead of "Northern Desert of Ro")
  if (typeof(zone) === 'string') zone = zoneData[zone];

  // Handle Multi-Level Zones (with different maps for 1st, 2nd, etc. floor)
  if (zone.levels) return getZoneLevelData(zone.levels, nonLocParts);

  // Handle Zones with alternate maps on the same level (eg. Kelethin in GFay)
  if (!zone.alternateMaps) return zone;

  for (var i = 0; i < zone.alternateMaps.length; i++) {
    var alternateData = zone.alternateMaps[i];
    var allLocsAreWithin = true;
    // wish I had ES6 [].every
    $.each(locs, function(i, loc) {
      allLocsAreWithin = allLocsAreWithin && 
                         locIsWithinAlternateData(loc, alternateData);
    });
    if (allLocsAreWithin) return alternateData;
  };
  return zone;
};

window.getZoneData = function(zoneName, locs, nonLocParts) {
  var zoneData = findZoneData(zoneName, locs, nonLocParts);
  if (!zoneData) return null;
  zoneData.zoneName = zoneName;
  zoneData.imageUrl = '/images/' + zoneData.image;
  return zoneData;
};

var build$MapImage = function(zoneData) {
  return $(
    '<img alt="Map of ' + zoneData.zoneName + '" ' +
    '     class="thumbborder" ' +
    '     height="'+ zoneData.height + '" ' +
    '     src="' + zoneData.imageUrl + '" ' +
    '     title="Map of ' + zoneData.zoneName + '"' +
    '     width="' + zoneData.width + '" ' +
    '>'
  );
};

var build$InnerWrapper = function(zoneData) {
  return $('<div style="position:relative"></div>')
    .css({
      left: '50%',
      marginLeft: '-' + (zoneData.width / 2) + 'px', // centering
      marginTop: '-' + (zoneData.height / 2) + 'px',
      opacity: 1,
      top: '50%'
    });
};

var build$OuterWrapper = function(text) {
  return $('<div class="map-wrapper" id="map-image-frame">' + text + '</div>')
    .css({
      background: 'rgba(0,0,0,0.8)',
      height: '100%',
      left: 0,
      position: 'fixed',
      top: 0,
      width: '100%',
      zIndex: 2
    })
    .click(function() {
      $(this).remove();
    });
};

/**
 * Adds a loc map image to the page.
 */
window.showMap = function(zoneName, loc) {
  // TODO: Separate loc into loc and non-loc parts
  var zoneData = window.getZoneData(zoneName, loc);

  var $parent = $('body')
    // if origin
    //  if origin above halfway point
    //    add an $parent element after origin
    //  else add it below
  var $frame = build$OuterWrapper(zoneName + ' has not been loc-mapped yet.');
  if (zoneData) {
    var $img = build$MapImage(zoneData);
    var $innerWrapper = build$InnerWrapper(zoneData).append($img);
    $frame = $frame.html($innerWrapper);
    $frame.zoneData = zoneData;
  }
  $parent.append($frame);
  return $frame;
};

/**
 * Draws a red "X" on the map at the provided coordinate
 */
var addXToMap = function($map, x, y) {
  var zone = $map.zoneData;
  var left = (zone.zeroX || 0) + x * -1 * (zone.zoomX || 0.1);
  var top = (zone.zeroY || 0) + y * -1 * (zone.zoomY || 0.1);
  //console.log('x of ' + x + ' became ' + left);
  //console.log('y of ' + y + ' became ' + top);
  //console.log(zone);
  $map
    .find('div')
    .append(
      $('<div class="x">x</div>')
        .css({
          color: 'red',
          fontSize: '3em',
          fontWeight: 'bold',
          left: left,
          position: 'absolute',
          top: top
        })
    );
};

// TODO: Refactor getLocs to use this
var parseLoc = function(locText) {
  var match = locText.match(/\(? *([\+\-]?\d+\.?\d*), *([\+\-]?\d+\.?\d*)\)?/);
  return {x: parseFloat(match[2]), y: parseFloat(match[1]) };
}



//var $map = showMap('Western Karana')
//addXToMap($map, -11139, -3508);
$('.loc-link').click(function() {
  var data = $(this).data();
  var $map = showMap(data.zone, data.loc);
  var loc = parseLoc(data.loc);
  addXToMap($map, loc.x, loc.y);
  return false
 });


})(); // module end
(function() {

  // TODO: Add support for cropping maps that have multiple maps.
  // For instance, the following CSS styles show only one of the three maps for 
  // Erudin Palace:
  // background-image:url(/images/Erudinpalace.jpg); 
  // background-position: 250px 0px; width: 275px; height: 272px
  // Figure out how to make the Xs line up with those styles added

  var locIsWithinAlternateData = function(loc, alternateData) {
    return loc.x < alternateData.maxX &&
        loc.x > alternateData.minX &&
        loc.y < alternateData.maxY &&
        loc.y > alternateData.minY;
  };

  /**
   * Determines if the provided name contains any of the provided words
   */
  // TODO: Add a some polyfill to make this a lot simpler
  var containsAny = function(name/* word1, word2, etc. */) {
    for(var arg = 1; arg < arguments.length; ++ arg) {
      var word = arguments[arg];
      if (name.includes(word)) return true;
    }
    return false;
  };

  var containsAnyNumber = function(name, number, suffix) {
    return containsAny.call(null, name, 
           number + suffix,
           'level ' + number,
           'level: ' + number
    );
  };

  var getZoneLevelData = function(levels, text) {
    // Check for part "level" aliases (eg. "1st floor" vs. "Level One")
    text = text.toLowerCase();
    if (containsAnyNumber(text, 0, 'th') || containsAny(text, 'basement', 'underground')) return levels[0];
    if (containsAnyNumber(text, 1, 'st') || containsAny(text, 'one', 'first', 'ground')) return levels[1];
    if (containsAnyNumber(text, 2, 'nd') || containsAny(text, 'two', 'second')) return levels[2];
    if (containsAnyNumber(text, 3, 'rd') || containsAny(text, 'three', 'third')) return levels[3];
    if (containsAnyNumber(text, 4, 'th') || containsAny(text, 'four', 'fourth')) return levels[4];
    if (containsAnyNumber(text, 5, 'th') || containsAny(text, 'five', 'fifth')) return levels[5];
    if (containsAnyNumber(text, 6, 'th') || containsAny(text, 'six', 'sixth')) return levels[6];
    if (containsAnyNumber(text, 7, 'th') || containsAny(text, 'seven', 'seventh')) return levels[7];
    if (containsAnyNumber(text, 8, 'th') || containsAny(text, 'eight', 'eighth')) return levels[8];
    if (containsAnyNumber(text, 9, 'th') || containsAny(text, 'nine', 'ninth')) return levels[9];

    // If we still couldn't match, and there is a ground/1st floor, use it
    return levels[1];
  };

  var getZoneData = function(zoneName, locs, nonLocParts) {
    var zone = zoneData[zoneName];

    // Handle aliases (eg. "North Ro" instead of "Northern Desert of Ro")
    if (typeof(zone) === 'string') zone = zoneData[zone];

    // Handle Multi-Level Zones (with different maps for 1st, 2nd, etc. floor)
    if (zone.levels) return getZoneLevelData(zone.levels, nonLocParts);

    // Handle Zones with alternate maps on the same level (eg. Kelethin in GFay)
    if (!zone.alternateMaps) return zone;

    for (var i = 0; i < zone.alternateMaps.length; i++) {
      var alternateData = zone.alternateMaps[i];
      var allLocsAreWithin = true;
      // wish I had ES6 [].every
      $.each(locs, function(i, loc) {
        allLocsAreWithin = allLocsAreWithin && locIsWithinAlternateData(loc, alternateData);
      });
      if (allLocsAreWithin) return alternateData;
    };
    return zone;
  };

  var isLoc = function(locBit) {
    // If we can't split the string by its comma and find a number on either side, it's not a loc
    try {
      return locBit.split(',')[0].match(/\d+/) && locBit.split(',')[1].match(/\d+/);
    } catch (err) {
      return false;
    }
  }

  /**
   * Extracts all of the locs (objects with x and y properties) from a provided
   * string such as:
   * "500, 200"
   * "(200, 300), (300, 200)"
   */
  var getLocs = function(locString) {
    var nonLocParts = '';
    return locString
      .split(/([\+\-]?\d+\.?\d*\D*,\D*[\+\-]?\d+\.?\d*)/g)
      .filter(function(part) {
        // Filter out the non-loc parts, but save them
        if (isLoc(part)) return true;
        nonLocParts += part; // save as it might be something like "1st floor"
      })
      .map(function(locText) {
        var match = locText.match(
            /\(? *([\+\-]?\d+\.?\d*), *([\+\-]?\d+\.?\d*)\)?/);
        return {x: parseFloat(match[2]), y: parseFloat(match[1]) };
      })
      .concat(nonLocParts);
  };

  /**
   * Draws a red "X" on the map at the provided coordinate
   */
  var makeX = function(x, y, zone, $img) {
    var left = (zone.zeroX || 0) + x * -1 * (zone.zoomX || 0.1);
    var top = (zone.zeroY || 0) + y * -1 * (zone.zoomY || 0.1);
    //console.log('x of ' + x + ' became ' + left);
    //console.log('y of ' + y + ' became ' + top);
    //console.log(zone);
    $img
    .parent()
    .css({position: 'absolute'})
    .append(
        $('<div class="x">x</div>')
        .css({
          color: 'red',
          fontSize: '2em',
          fontWeight: 'bold',
          left: left,
          position: 'absolute',
          top: top
        }));
  };

  /**
   * Adds a loc map to the page (presumably in response to a user
   * mousing over a loc map link)
   */
  var addMap = function($locTd, imageName, width, height) {
    if ($('.map-wrapper').length) return;

    var imageUrl = '/images/'+ imageName;
    // Allow local files for testing purposes
    if (imageName.indexOf('file') === 0) imageUrl = imageName;

    var $img = $('<img alt="' + imageName + '" ' +
        'src="' + imageUrl + '" ' +
        'width="' + width + '" ' +
        'height="'+ height + '" ' +
        'class="thumbborder" ' +
        'title="' + imageName + '">' );
    $locTd.append(
        $('<div class="map-wrapper" style="position:relative"></div>')
        .append($img)
    );
  };

  // Define two helper functions for building new zone definitions

  // 1) Use this function to find the correct 0,0 point
  window.testZeroZero = function(zone) {
    var $locTd = $('b:contains("Location:")').parent();
    $('.x').remove();
    var $img = $('img[alt="' + zone.image + '"]').show()
    addMap($locTd, zone.image, zone.width, zone.height);
    makeX(0, 0, zone, $img);
  };

  // 1.5) Use this function to find a different point (when the map doesn't show zero, zero)
  window.testPoint = function(zone, y, x) {
    var $locTd = $('b:contains("Location:")').parent();
    $('.x').remove();
    var $img = $('img[alt="' + zone.image + '"]').show()
    addMap($locTd, zone.image, zone.width, zone.height);
    makeX(x, y, zone, $img);
  };

  // 2) Use this function to generate a grid of alignment of X's
  window.testGrid = function(zone) {
    var $locTd = $('b:contains("Location:")').parent();
    $('.x').remove();
    addMap($locTd, zone.image, zone.width, zone.height);
    var $img = $('img[alt="' + zone.image + '"]').show()
    for (var x = zone.maxX; x >= zone.minX; x -= zone.interval) {
      for (var y = zone.maxY; y >= zone.minY; y -= zone.interval) {
        makeX(x, y, zone, $img);
      }
    };
  };

  try {
    // Get the mob's loc(s)
    var $locTd = $('b:contains("Location:")').parent();
    var locs = getLocs($locTd.text());
    if (locs.length <= 1) return;
    var nonLocParts = locs[locs.length - 1];
    locs = locs.slice(0, locs.length - 1);


    // Find the zone name
    var $zoneTd = $('b:contains("Zone:")').parent().text();
    var zoneName = $zoneTd.split('Zone:')[1].trim();

    // Do we have data for that zone's map?
    var zone = getZoneData(zoneName, locs, nonLocParts);
    if (!zone) return;  // If not, stop here

    // Add the mouseover link
    var $link = $(' <a href="#">(Map)</a>');

    // When it's moused-over, show the map
    $link
    .on('mouseover', function(e) {
      addMap($locTd, zone.image, zone.width, zone.height);
      $.each(locs, function(i, loc) {
        makeX(loc.x, loc.y, zone, $('img[alt="' + zone.image + '"]'));
      });
    })
    .on('mouseleave', function(e) {
      if (this.wasClicked) {
        this.wasClicked = false;
      } else {
        $('.map-wrapper').remove();
      }
      return false;
    })
    .on('click', function(e) {
      if ($(e.target).text() === '(Hide Map)') {
        $(e.target).text('(Show on Map)');
        $('.map-wrapper').remove();
      } else  {
        $(e.target).text('(Hide Map)');
        addMap($locTd, zone.image, zone.width, zone.height);
        $.each(locs, function(i, loc) {
          makeX(loc.x, loc.y, zone, $('img[alt="' + zone.image + '"]'));
        });
        this.wasClicked = true;
      }
      return false;
    });

    // $locTd.find('b').html($('<span>Location </span>').append($link).append('<span>:</span>'));
    $locTd.find('b').html($('<span>Location </span>').append($link).append('<span>:</span>'));
  } catch (err) {/* Didn't work, move on */}


})();