[ Disclaimer, Create new user --- Wiki markup help, Install P99 ]
MediaWiki:LocMaps.js
From Project 1999 Wiki
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);
}
};
};
})();