[ 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
// 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
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 */}
})();