Files
2025-09-29 00:52:08 +02:00

532 lines
14 KiB
JavaScript
Executable File

//var width = 700,
// height = 800,
var vBoxWidth = 1200,
vBoxHeight = 900,
bgWidth = 900,
bgHeight = 1500,
clicked;
//Object used for local storage of the user selections - had to use underscores instead of hyphen
/*
var pageData = {
deathmatch_list: 0,
time_since_spawn: 10,
}
*/
//var interpolationColours = ["#55ff00", "#fffc00", "#ff0400"]; // green - yellow - red
function convert2WebXCoord(x) {
return ((x + project.map.coords.x)/project.map.scale);
}
function convert2WebYCoord(y) {
return (((-1)*y + project.map.coords.y)/project.map.scale);
}
function convert2WebCoords(p) {
return [convert2WebXCoord(p[0]), convert2WebYCoord(p[1])];
}
/*
function convertFromWebXCoord(x) {
return (-1) * ((project.map.coords.x) - (x*project.map.scale));
}
function convertFromWebYCoord(y) {
return ((project.map.coords.y) - (y*project.map.scale));
}
*/
/*
function updateMapHeight() {
// Adjust the height of the map automatically
var windowHeight = $("#content").height() - 20;
//$("#map-wrapper").css("height", windowHeight);
$("#map").css("height", windowHeight);
$("#map").css("width", "70%");
$("#map-side-options").css("height", windowHeight);
}
*/
var headerAndFilters = {
headerType: "header-sc-map", // social club filtering header
disabledFields: // disables header fields
[
false, // platforms
false, // locations
false, // age
false, // gamers
false, // game-types
false, // dates+builds
],
};
function initPage() {
initHeaderAndFilters(headerAndFilters);
//var localData = retrieveLocalObject(config.currentFilename);
//pageData = (localData) ? localData : pageData;
//pageData.deathmatch_list
$("#content-body")
.append(
$("<div>")
.attr("id", "map-wrapper")
.append(
$("<fieldset>")
.attr("id", "map")
.append(
$("<legend>").text("Map")
)
)
.append(
$("<div>")
.attr("id", "map-side-options")
)
);
block();
$.ajax({
url: project.map.svgFile,
type: "GET",
data: {},
dataType: "xml",
async: false,
success: function(xml, textStatus, jqXHR) {
var importedNode;
try {
importedNode = document.importNode(xml.documentElement, true);
}
catch(e) {
// IE case
importedNode = ieImportNode(xml.documentElement, document);
}
$("#map").append(importedNode);
},
error: function (xhr, ajaxOptions, thrownError){
console.error(this.url + "\n" + ajaxOptions + " " + xhr.status + " " + thrownError );
},
complete: function() {
var svg = d3.select("#map svg")
.attr("width", $("#map").width())
.attr("height", $("#map").height())
.attr("enable-background", "new 0 0 " + bgWidth + " "+ bgHeight)
.call(svg_interact);
drawGrid();
}
});
unBlock();
$("#map-side-options")
.append(
$("<fieldset>")
.attr("id", "map-layers")
.append(
$("<legend>").text("Map Layers")
)
.append(
$("<input>")
.attr("type", "checkbox")
.attr("id", "map-layer-grid")
.attr("checked", true)
.val("grid")
)
.append(
$("<label>")
.attr("for", "map-layer-grid")
.text("Grid")
)
.append($("<br />"))
.append(
$("<input>")
.attr("type", "checkbox")
.attr("id", "map-layer-sea")
.attr("checked", true)
.val("sea")
)
.append(
$("<label>")
.attr("for", "map-layer-sea")
.text("Sea")
)
.append($("<br />"))
.append(
$("<input>")
.attr("type", "checkbox")
.attr("id", "map-layer-landcity")
.attr("checked", true)
.val("landcity")
)
.append(
$("<label>")
.attr("for", "map-layer-landcity")
.text("Landcity")
)
.append($("<br />"))
.append(
$("<input>")
.attr("type", "checkbox")
.attr("id", "map-layer-landbeach")
.attr("checked", true)
.val("landbeach")
)
.append(
$("<label>")
.attr("for", "map-layer-landbeach")
.text("Land Beach")
)
.append($("<br />"))
.append(
$("<input>")
.attr("type", "checkbox")
.attr("id", "map-layer-landgreen")
.attr("checked", true)
.val("landgreen")
)
.append(
$("<label>")
.attr("for", "map-layer-landgreen")
.text("Landgreen")
)
.append($("<br />"))
.append(
$("<input>")
.attr("type", "checkbox")
.attr("id", "map-layer-tunnels")
.attr("checked", true)
.val("tunnels")
)
.append(
$("<label>")
.attr("for", "map-layer-tunnels")
.text("Tunnels")
)
.append($("<br />"))
.append(
$("<input>")
.attr("type", "checkbox")
.attr("id", "map-layer-metro")
.attr("checked", true)
.val("metro")
)
.append(
$("<label>")
.attr("for", "map-layer-metro")
.text("Metro")
)
.append($("<br />"))
.append(
$("<input>")
.attr("type", "checkbox")
.attr("id", "map-layer-roads")
.attr("checked", true)
.val("roads")
)
.append(
$("<label>")
.attr("for", "map-layer-roads")
.text("Roads")
)
.append($("<br />"))
.append(
$("<input>")
.attr("type", "checkbox")
.attr("id", "map-layer-rail")
.attr("checked", true)
.val("rail")
)
.append(
$("<label>")
.attr("for", "map-layer-rail")
.text("Rail")
)
.append($("<br />"))
.append(
$("<input>")
.attr("type", "checkbox")
.attr("id", "map-layer-telemetry")
.attr("checked", true)
.val("telemetry")
)
.append(
$("<label>")
.attr("for", "map-layer-telemetry")
.text("Telemetry")
)
.append($("<br />"))
);
// End of adding grid layers
/*
// Bind the events
$(".overlay-options select").change(function() {
pageData[$(this).attr("id").replace(/\-/g, '_')] = $(this).val();
storeLocalObject(config.currentFilename, pageData);
generateCustomOverlay();
});
*/
/*
$("input#time-since-spawn").change(function() {
pageData[$(this).attr("id").replace(/\-/g, '_')] = $(this).val();
storeLocalObject(config.currentFilename, pageData);
generateCustomOverlay();
});
*/
//$("#map-layers :checkbox").click(function(){
// The following declaration is faster than the latter
$("#map-layers input[type=checkbox]").click(function() {
var layer = d3.select("#map svg").select("g#" + $(this).val());
if ($(this).is(":checked")) {
layer
.transition()
.attr("opacity", 1);
}
else {
layer
.transition()
.attr("opacity", 0.01);
}
});
//updateMapHeight();
//$(window).resize(updateMapHeight);
$("#filter").click(function() {
generateCustomOverlay();
});
generateCustomOverlay();
}
function drawGrid() {
var gridStep = 500/project.map.scale; // The step of the grid in map coordinates
var grid = d3.select("#map svg")
.append("svg:g")
.attr("id", "grid");
var xAxisDataNeg = d3.range(convert2WebXCoord(0), 0, -gridStep);
var xAxisDataPos = d3.range(convert2WebXCoord(0), bgWidth, gridStep);
var yAxisDataNeg = d3.range(convert2WebYCoord(0), 0, -gridStep);
var yAxisDataPos = d3.range(convert2WebYCoord(0), bgHeight, gridStep);
grid.selectAll("line.vertical")
.data(xAxisDataNeg.concat(xAxisDataPos))
.enter()
.append("svg:line")
.attr("x1", function(d) {return d;})
.attr("y1", 0)
.attr("x2", function(d) {return d;})
.attr("y2", bgHeight)
.attr("class", "map-grid");
grid.selectAll("line.horizontal")
.data(yAxisDataNeg.concat(yAxisDataPos))
.enter()
.append("svg:line")
.attr("x1", 0)
.attr("y1", function(d) {return d;})
.attr("x2", bgWidth)
.attr("y2", function(d) {return d;})
.attr("class", "map-grid");
}
function drawSpawnHeatmap() {
/*
var radius = 2,
UGCId = $("select#deathmatch-list").val(),
timeSinceSpawn = $("input#time-since-spawn").val();
// get the social club header filtering parameter values, headerAndFilters.headerType comes from local conf
var pValues = config.headerOptions[headerAndFilters.headerType].getParamValues();
console.log(pValues;
// Add the extra Spaw Points param values
pValues.Pairs["DeathmatchUGCIdentifier"] = UGCId;
pValues.Pairs["BlockSize"] = radius;
pValues.Pairs["TimeSinceSpawn"] = timeSinceSpawn;
*/
var pValues = config.headerOptions[headerAndFilters.headerType].getParamValues();
if (!pValues)
return;
//console.log(pValues);
//console.log(pValues.Pairs["BlockSize"]);
if (!pValues.Pairs["DeathmatchUGCIdentifier"] || !pValues.Pairs["TimeSinceSpawn"])
return;
var req = new ReportRequest(config.restHost + config.mapDeathmatchStats,
"json",
config.restHost + config.mapDeathmatchStatsAsync + pValues.ForceUrlSuffix,
config.restHost + config.reportsQueryAsync);
req.sendSingleAsyncRequest(pValues, handleSpawnResults);
function handleSpawnResults(telemetryData) {
showMsgWhenNoData(telemetryData);
if (telemetryData.length == 0)
return;
var valueFunc = function(d) {return d.Occurrences};
var xFunc = function(d) {return d.Location.X};
var yFunc = function(d) {return d.Location.Y};
generateCoolHeatmap(telemetryData, pValues.Pairs["BlockSize"], valueFunc, xFunc, yFunc);
}
}
function generateCoolHeatmap(telemetryData, radius, valueFunc, xFunc, yFunc) {
// Clean any canvas elements
$("#map canvas").remove();
var config = {
"radius": 10, // This is only for heatmap.js lib, different than the blocksize
"element": "map",
"visible": true,
"opacity": 60,
"gradient": { 0: "rgb(0,255,0)", 0.5: "yellow", 1: "rgb(255,0,0)" }
};
var heatmap = heatmapFactory.create(config);
var heatmapData = {
max: 0,
data: [],
};
// Var for the max value
var max = 0;
// We need to find the rectangle that includes all the points so we can generate only a small
// heatmap image of the desired area.
var upperLeft = {x: telemetryData[0].Location.X, y: telemetryData[0].Location.Y};
var lowerRight = {x: telemetryData[0].Location.X, y: telemetryData[0].Location.Y};
$.each(telemetryData, function(i, point) {
// find the maximum value
max = (max < valueFunc(point)) ? valueFunc(point) : max;
// We need to find the upperLeft and lowerRight points in real game coordinates that include all the points
// upperLeft has minimum X and maximum Y
upperLeft.x = (xFunc(point) < upperLeft.x) ? xFunc(point) : upperLeft.x;
upperLeft.y = (yFunc(point) > upperLeft.y) ? yFunc(point) : upperLeft.y;
// lowerRight has maximum X and minimum or equal Y
lowerRight.x = (xFunc(point) > lowerRight.x) ? xFunc(point) : lowerRight.x;
lowerRight.y = (yFunc(point) < lowerRight.y) ? yFunc(point) : lowerRight.y;
});
// Save the max value to the heatmap data
heatmapData["max"] = max;
var margin = 50;
// Add the radius + margin to the rectangle corner points
upperLeft.x -= (radius + margin);
upperLeft.y += (radius + margin);
lowerRight.x += (radius + margin);
lowerRight.y -= (radius + margin);
//console.log(max);
//console.log(upperLeft);
//console.log(lowerRight);
// The width and the height of the heatmap fragment will be
fragmentWidth = lowerRight.x - upperLeft.x;
fragmentHeight = upperLeft.y - lowerRight.y;
//console.log(fragmentWidth);
//console.log(fragmentHeight);
// Crate a new fragment coordinates system where 0.0 is the top left
// new (0, 0) will be (upperLeft.x, upperLeft.y)
function convertXtoFragmentCoords(x) {
// Subtract new (0, 0) from the x coord, upperLeft.x is the minimum x
return x - upperLeft.x;
}
function convertYtoFragmentCoords(y) {
// Subtract y coord from the new (0, 0), upperLeft.y is the maximum y
return upperLeft.y - y;
}
$("#map canvas")
.attr("id", "heatmapjs-canvas")
.attr("width", fragmentWidth)
.attr("height", fragmentHeight)
.css("display", "none");
$.each(telemetryData, function(i, point) {
heatmapData.data.push({x: convertXtoFragmentCoords(xFunc(point)),
y: convertYtoFragmentCoords(yFunc(point)),
count: valueFunc(point)});
})
heatmap.store.setDataSet(heatmapData);
//console.log(heatmapData);
// Save the canvas to a file object as png image
var canvas = document.getElementById("heatmapjs-canvas");
var image = canvas.toDataURL("image/png");
//window.location = image;
// Add the scaled image to the correct position on the svg map (use the previously calculated upperLeft point)
var imageHeatmap =
d3.select("#map svg")
.append("svg:g")
.attr("id", "telemetry")
imageHeatmap.append("svg:g")
.append("svg:image")
.attr("xlink:href", image)
.attr("width", fragmentWidth/project.map.scale)
.attr("height", fragmentHeight/project.map.scale)
.attr("x", function() {return convert2WebXCoord(upperLeft.x)})
.attr("y", function() {return convert2WebYCoord(upperLeft.y)});
imageHeatmap
.append("svg:g")
.append("svg:rect")
.attr("x", function() {
return convert2WebXCoord(upperLeft.x);
})
.attr("y", function() {
return convert2WebYCoord(upperLeft.y);
})
.attr("width", fragmentWidth/project.map.scale)
.attr("height", fragmentHeight/project.map.scale)
.attr("stroke", config.chartColour1)
.attr("stroke-width", 2)
.attr("fill-opacity", 0.3)
.append("title")
.text(function() {
return $("select#deathmatch-list :selected").html();
})
}
// end of generateCoolHeatmap
function removeOverlay() {
d3.select("#map svg")
.selectAll("g#telemetry")
.transition()
.attr("opacity", 0.01)
.remove();
}
function generateCustomOverlay() {
removeOverlay();
drawSpawnHeatmap();
}
function showMsgWhenNoData(array) {
if (array.length < 1)
Sexy.alert(config.noDataMapText);
}