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 23:15, 2 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
/**
 * Pseudo-module #1: getZoneData
 */
window.getZoneData = (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 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;
};

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

})();

/**
 * Pseudo-module #2
 */
(function() {


  /**
   * This basic function serves as the click handler for "lightbox-style" maps
   */
  var removeSelf = function() {
    $(this).remove();
  };


  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$WrappedMap = function(zoneData) {
    var $img = build$MapImage(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%'
    }).append($img);
  };

  var build$MapFrame = function(text) {
    text = text || '';
    return $('<div class="map-wrapper">'+ text + '</div>')
            .css({ position: 'relative' });
  };

  var build$FullScreenMap = function(zoneData) {
    var defaultText = zoneName + ' has not been loc-mapped yet.';
    var $frame = build$MapFrame(defaultText)
      .css({
        background: 'rgba(0,0,0,0.8)',
        height: '100%',
        left: 0,
        position: 'fixed',
        top: 0,
        width: '100%',
        zIndex: 4 // one higher than #p-search's 3
      })
      .click(removeSelf);
    if (zoneData) {
      $frame.html(build$WrappedMap(zoneData));
      $frame.zoneData = zoneData;
    }
    return $frame;
  };

  var build$SmallMap = function(zoneData) {
    return build$MapFrame().append(build$MapImage(zoneData));
  }


/**
 * Adds a loc map image to the page.
 */
 var 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 $map = build$FullScreenMap(zoneData)
  $parent.append($map);
  return $map;
};

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

  var $map = build$SmallMap(zoneData);
  $locTd.append($map);
  return $map;
};

var buildX = function(left, top, sizeInEm) {
  sizeInEm = sizeInEm || 2;
  return $('<div class="x">x</div>')
    .css({
      color: 'red',
      fontSize: sizeInEm + 'em',
      fontWeight: 'bold',
      left: left,
      position: 'absolute',
      top: top
    })
}

/**
 * Draws a red "X" on the map at the provided coordinate
 */
var addX = function($map, x, y, zoneData, xSize) {
  var left = (zoneData.zeroX || 0) + x * -1 * (zoneData.zoomX || 0.1);
  var top = (zoneData.zeroY || 0) + y * -1 * (zoneData.zoomY || 0.1);
  $map.append(buildX(left, top, xSize));
}

var addXs = function(zoneData, $map, locs) {
  $.each(locs, function(i, loc) {
    addX($map.parent().css({position: 'absolute'}), loc.x, loc.y, zoneData);
  });
}


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

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(parseLoc)
    .concat(nonLocParts);
  };

// TODO: THis is called Loc*s* but it only handles 1
  var showMapWithLocs = function(zoneData, locs) {
    var $map = showMap(zoneData, locs[0]);
    var loc = parseLoc(locs[0]);

    $map.find('img').load(function() {
      addX($map.find('div'), loc.x, loc.y, zoneData, 3);
    });
  }



  // TODO: merge addSmallMapIfNoneExists and showMap

  
  try {
    // Handle "loc links" (generated from {{loc}} templates)
    $('.loc-link').click(function() {
      var data = $(this).data();
      
      showMapWithLocs(data.zone, [data.loc]);
      return false
    });

    // 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 zoneData = getZoneData(zoneName, locs, nonLocParts);
    if (!zoneData) 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) {
      var $map = addMap($locTd, zoneData);
      addXs(zoneData, $map, locs);
    })
    .on('mouseleave', function(e) {
      if (this.wasClicked) {
        this.wasClicked = false;
      } else {
        $('.map-wrapper').remove();
      }
      return false;
    })
    .on('click', function(e) {
      // TODO: Use showMap above instead
      var $link = $(e.target);
      if ($link.text() === '(Hide Map)') {
        $link.text('(Show on Map)');
        $('.map-wrapper').remove();
      } else  {
        $link.text('(Hide Map)');
        addSmallMapIfNoneExists($locTd, zoneData);
        $.each(locs, function(i, loc) {
          var $map = $('img[alt="' + zoneData.image + '"]')
             // parent?!?
            .parent().css({position: 'absolute'});
          addX($map, loc.x, loc.y, zoneData);
        });
        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 */}

  
  // Define two helper functions for building new zone definitions

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


  // 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();
    addSmallMapIfNoneExists($locTd, zoneData);
    var $img = $('img[alt="' + zoneData.image + '"]').show()
    for (var x = zoneData.maxX; x >= zoneData.minX; x -= zoneData.interval) {
      for (var y = zoneData.maxY; y >= zoneData.minY; y -= zoneData.interval) {
        addX($img.parent().css({position: 'absolute'}), x, y, zoneData);
      }
    };
  };

})();