diff options
-rw-r--r-- | css/style.css | 4 | ||||
-rw-r--r-- | index.html | 26 | ||||
-rw-r--r-- | js/mv.js | 332 | ||||
-rw-r--r-- | js/mv/chart.js | 107 | ||||
-rw-r--r-- | js/mv/heap.js | 34 | ||||
-rw-r--r-- | js/mv/load.js | 60 | ||||
-rw-r--r-- | js/mv/main.js | 57 | ||||
-rw-r--r-- | js/mv/parse.js | 86 |
8 files changed, 361 insertions, 345 deletions
diff --git a/css/style.css b/css/style.css index 486ef83..0efc85b 100644 --- a/css/style.css +++ b/css/style.css @@ -13,11 +13,11 @@ body { overflow: hidden; } -#body, #main, #status, .side { +body, #main, #status, .side { min-height: 40px; } -#body { +body { min-width: 1350px; } @@ -4,23 +4,23 @@ <link rel="stylesheet" type="text/css" href="css/style.css" /> <link rel="stylesheet" type="text/css" href="js/dc/test/dc.css" /> -<div id="body"> +<body onload="mv.init();"> <div class="side"> <div class="vis-hide"> <div id="blvl-chart"> - <h3>Experience gain instances by Character Base Level <a class="reset" style="display: none;" href="javascript:mv.blvlChart.filterAll();dc.redrawAll();">clear</a></h3> + <h3>Experience gain instances by Character Base Level <a class="reset" style="display: none;" href="javascript:mv.charts.blvl.filterAll();dc.redrawAll();">clear</a></h3> </div> <div id="type-chart"> - <h3>Experience gain instances by Type <a class="reset" style="display: none;" href="javascript:mv.typeChart.filterAll();dc.redrawAll();">clear</a></h3> + <h3>Experience gain instances by Type <a class="reset" style="display: none;" href="javascript:mv.charts.type.filterAll();dc.redrawAll();">clear</a></h3> </div> <div id="target-chart"> - <h3>Experience gain instances by Target <a class="reset" style="display: none;" href="javascript:mv.targetChart.filterAll();dc.redrawAll();">clear</a></h3> + <h3>Experience gain instances by Target <a class="reset" style="display: none;" href="javascript:mv.charts.target.filterAll();dc.redrawAll();">clear</a></h3> </div> <div id="wpn-chart"> - <h3>Experience gain instances by Weapon <a class="reset" style="display: none;" href="javascript:mv.wpnChart.filterAll();dc.redrawAll();">clear</a></h3> + <h3>Experience gain instances by Weapon <a class="reset" style="display: none;" href="javascript:mv.charts.wpn.filterAll();dc.redrawAll();">clear</a></h3> </div> <div id="def-chart"> - <h3>Definedness of records <span class="help" title="If logs are missing between server boot and the logs provided, not all information will be available for all records. Definedness of records falls into three categories. Records with undefined data; records with well defined data, but mixed in with records having undefined data, limiting validity of inferences; and records after any uncertain times, which are fully well defined. It is highly recommended that you filter results to only include records which are in fully well defined times; however, this may not always be possible.">[?]</span> <a class="reset" style="display: none;" href="javascript:mv.defChart.filterAll();dc.redrawAll();">clear</a></h3> + <h3>Definedness of records <span class="help" title="If logs are missing between server boot and the logs provided, not all information will be available for all records. Definedness of records falls into three categories. Records with undefined data; records with well defined data, but mixed in with records having undefined data, limiting validity of inferences; and records after any uncertain times, which are fully well defined. It is highly recommended that you filter results to only include records which are in fully well defined times; however, this may not always be possible.">[?]</span> <a class="reset" style="display: none;" href="javascript:mv.charts.def.filterAll();dc.redrawAll();">clear</a></h3> </div> </div> </div> @@ -44,17 +44,17 @@ </div> <div class="vis-hide"> <div id="map-chart"> - <h3>Experience gain by Map <span class="help" title="Bubble size indicates instances of experience gain for that map. X axis position indicates the sum of level experience gain for that map. Y axis position indicates the sum of job experience gain for that map.">[?]</span> <a class="reset" style="display: none;" href="javascript:mv.mapChart.filterAll();dc.redrawAll();">clear</a></h3> + <h3>Experience gain by Map <span class="help" title="Bubble size indicates instances of experience gain for that map. X axis position indicates the sum of level experience gain for that map. Y axis position indicates the sum of job experience gain for that map.">[?]</span> <a class="reset" style="display: none;" href="javascript:mv.charts.map.filterAll();dc.redrawAll();">clear</a></h3> </div> <div id="date-chart"> - <h3>Experience gain instances by Date <a class="reset" style="display: none;" href="javascript:mv.dateChart.filterAll();dc.redrawAll();">clear</a></h3> + <h3>Experience gain instances by Date <a class="reset" style="display: none;" href="javascript:mv.charts.date.filterAll();dc.redrawAll();">clear</a></h3> </div> <div id="player-chart"> - <h3>Experience gain instances by Character ID <span class="help" title="Older to newer characters appear left to right, respectively.">[?]</span> <a class="reset" style="display: none;" href="javascript:mv.pcChart.filterAll();dc.redrawAll();">clear</a></h3> + <h3>Experience gain instances by Character ID <span class="help" title="Older to newer characters appear left to right, respectively.">[?]</span> <a class="reset" style="display: none;" href="javascript:mv.charts.pc.filterAll();dc.redrawAll();">clear</a></h3> </div> </div> </div> -</div> +</body> <!-- Libs --> <script src="js/util/memoize.js"></script> @@ -68,4 +68,8 @@ <script src="js/comp/stat.js"></script><!-- Depends on crossfilter for crossfilter.bisect --> <!-- Processing --> -<script src="js/mv.js"></script> +<script src="js/mv/chart.js"></script> +<script src="js/mv/heap.js"></script> +<script src="js/mv/load.js"></script> +<script src="js/mv/main.js"></script> +<script src="js/mv/parse.js"></script> diff --git a/js/mv.js b/js/mv.js deleted file mode 100644 index b4910ed..0000000 --- a/js/mv.js +++ /dev/null @@ -1,332 +0,0 @@ -"use strict"; -/* - * Globals accessible via the agent console for debugging purposes - */ -var mv = {}; -/* - * Processing - */ -(function() { - function softAssert(expr, msg) { - if (!expr) { - console.log("SOFTASSERT FAILURE: " + msg); - } - } - /* Set up handlers for file selector */ - document.getElementById('input').addEventListener('change', function(fevt) { - var reader = new FileReader(); - var loadbar = progress('loadbar'); - var filesbar = progress('filesbar'); - - reader.onerror = function(evt) { - switch(evt.target.error.code) { - case evt.target.error.NOT_FOUND_ERR: - alert('File Not Found!'); - break; - case evt.target.error.NOT_READABLE_ERR: - alert('File is not readable'); - break; - case evt.target.error.ABORT_ERR: - break; // noop - default: - alert('An error occurred reading this file.'); - }; - } - reader.onprogress = function(evt) { - if (evt.lengthComputable) { - loadbar.update(evt.loaded, evt.total); - } - } - reader.onabort = function(evt) { - alert('File load aborted!'); - } - reader.onloadstart = function(evt) { - loadbar.reset(); - } - reader.onload = function(evt) { - loadbar.complete(); - ++cur; - parseRecords(reader.result); - if (cur == fevt.target.files.length) { - filesbar.complete(); - /* TODO: Make this fade out nicely? */ - setTimeout(function() { - loadbar.hide(); - }, 2000); - makeHeap(); - setTimeout(function() { - filesbar.hide(); - }, 2000); - makeCharts(); - } else { - filesbar.update(cur, fevt.target.files.length); - nextFile(); - } - } - var cur = 0; - var lbase = loadbar.label; - loadbar.label = function() { - return lbase() == '100%' ? "Loaded '" + fevt.target.files[cur].name + "' - Done!" : "Loading '" + fevt.target.files[cur].name + "' - " + lbase(); - }; - var fbase = filesbar.label; - filesbar.label = function () { - return fbase() == '100%' ? "Loaded " + fevt.target.files.length + " file(s) - Done!" : "Loading file " + (cur + 1) + " of " + fevt.target.files.length + " - " + fbase(); - } - loadbar.show(); - filesbar.show(); - function nextFile() { - reader.readAsBinaryString(fevt.target.files[cur]); - } - nextFile(); - }, false); - var records = []; - var pcstat = {}; - var fullyDefinedCutoff = false; - function defLevelVerbose(level) { - switch (level) { - case 0: return "Undefined"; - case 1: return "Mixed"; - case 2: return "Defined"; - default: console.log(d, d.data); throw "Unknown definedness case (" + d.data.key + "); this shouldn't happen"; - } - } - function parseRecords(data) { - var spl = data.split(/\r?\n/); - spl.forEach(function(e, i) { - var d; - d = e.match(/^(\d+\.\d+) PC(\d+) (\d+):(\d+),(\d+) GAINXP (\d+) (\d+) (\w+)/); - if (d) { - var mapSID = parseInt(d[3]); - var ts = new Date(0); - ts.setUTCSeconds(d[1]); - var rec = { - date: ts, - pc: parseInt(d[2]), - map: map.nameByServerID(parseInt(d[3]), ts), - x: parseInt(d[4]), - y: parseInt(d[5]), - e: parseInt(d[6]), - j: parseInt(d[7]), - type: d[8], - pcstat: pcstat[d[2]], - target: 0, - dmg: 0, - wpn: 0 - }; - if (pcstat[d[2]] == undefined && (!fullyDefinedCutoff || ts > fullyDefinedCutoff)) { - fullyDefinedCutoff = ts; - } - /* XXX: Fragile horrible and unstructured, this whole thing needs a rewrite really */ - if (i >= 2 && rec.type == "KILLXP") { - d = spl[i - 1].match(/^(\d+\.\d+) MOB(\d+) DEAD/); - if (d) { - var mID = parseInt(d[2]); - /* There's a massive wealth of data that can be collected from this. Number of assailants, weapons used, the relationships with the assailants... this can't be done with a simple lookbehind. For now, just extract what mob it was, and what the killing weapon used was. */ - d = spl[i - 2].match(/^(\d+\.\d+) PC(\d+) (\d+):(\d+),(\d+) WPNDMG MOB(\d+) (\d+) FOR (\d+) WPN (\d+)/); - if (d) { - softAssert(mID == parseInt(d[6]), "Integrity error: MOB ID mismatch!"); -// softAssert(rec.pc == parseInt(d[2]), "Integrity error: PC ID mismatch!"); - rec.target = parseInt(d[7]); - rec.dmg = parseInt(d[8]); - rec.wpn = parseInt(d[9]); - } - } else { - d = spl[i - 1].match(/^(\d+\.\d+) PC(\d+) (\d+):(\d+),(\d+) GAINXP (\d+) (\d+) (\w+)/); - if (d) { - var clone = records[records.length - 1]; - softAssert(rec.map == clone.map, "Integrity error: MAP ID mismatch!"); - rec.target = clone.target; - rec.dmg = clone.dmg; /* FIXME: Take into account actual assist damage */ - rec.wpn = clone.wpn; - } - } - } - records.push(rec); - return; - } - d = e.match(/^(?:\d+\.\d+) PC(\d+) (?:\d+):(?:\d+),(?:\d+) STAT (\d+) (\d+) (\d+) (\d+) (\d+) (\d+) /); - if (d) { - var s = { - str: parseInt(d[2]), - agi: parseInt(d[3]), - vit: parseInt(d[4]), - int: parseInt(d[5]), - dex: parseInt(d[6]), - luk: parseInt(d[7]) - }; - s.blvl = stat.minLevelForStats(s.str, s.agi, s.vit, s.int, s.dex, s.luk); - pcstat[d[1]] = s; - return; - } - }); - } - var cfdata, all, - dateDim, dateGroup, - pcDim, pcGroup, - mapDim, mapGroup, - blvlDim, blvlGroup, - typeDim, typeGroup, - targetDim, targetGroup, - dmgDim, dmgGroup, - wpnDim, wpnGroup, - /* - * How well defined a record is. - * 0 -> Record contains undefined data - * 1 -> Record is defined, but undefined records follow and may impede validity of findings - * 2 -> Record and all succeeding records are well defined - */ - defDim, defGroup; - /* The record files are set, do everything */ - function makeHeap() { - function a(p, d) { return { e: p.e + d.e, j: p.j + d.j, r: p.r + 1 }; } - function s(p, d) { return { e: p.e - d.e, j: p.j - d.j, r: p.r - 1 }; } - function z(p, d) { return { e: 0, j: 0, r: 0 }; } - cfdata = crossfilter(records); - all = cfdata.groupAll().reduce(a, s, z); - dateDim = cfdata.dimension(function(d) { return d3.time.hour.round(d.date); }); - dateGroup = dateDim.group().reduceCount(); - pcDim = cfdata.dimension(function(d) { return d.pc; }); - pcGroup = pcDim.group().reduceCount(); - mapDim = cfdata.dimension(function(d) { return d.map; }); - mapGroup = mapDim.group().reduce(a, s, z); - blvlDim = cfdata.dimension(function(d) { return d.pcstat ? d.pcstat.blvl : 0; }); - blvlGroup = blvlDim.group().reduceCount(); - typeDim = cfdata.dimension(function(d) { return d.type; }); - typeGroup = typeDim.group().reduceCount(); - targetDim = cfdata.dimension(function(d) { return d.target; }); - targetGroup = targetDim.group().reduceCount(); - wpnDim = cfdata.dimension(function(d) { return d.wpn; }); - wpnGroup = wpnDim.group().reduceCount(); - /* Add new dimensions above here */ - defDim = cfdata.dimension(function(d) { if (d.pcstat == undefined) { return 0; } if (d.date <= fullyDefinedCutoff) { return 1; } return 2; }); - defGroup = defDim.group().reduceCount(); - defDim.filterExact(2); - /* - * The viewport is the bubble frame. - * - K: Map - * - X: Exp - * - Y: JExp - * - r: Record count - */ - } - function makeCharts() { - d3.select("#mask") - .transition() - .style("opacity", 0) - .remove(); - d3.selectAll(".vis-hide") - .style("display", "inline") - .transition() - .style("opacity", 1) - ; - mv.dateChart = dc.barChart("#date-chart") - .width(700) - .height(130) - .margins({left: 60, right: 18, top: 5, bottom: 30}) - .dimension(dateDim) - .group(dateGroup) - .centerBar(true) - .gap(1) - .elasticY(true) - .elasticX(true) - .x(d3.time.scale().domain([dateDim.bottom(1)[0].date, dateDim.top(1)[0].date]).nice(d3.time.hour)) - .xUnits(d3.time.hours) - .xAxisPadding(2) - // .renderVerticalGridLines(true) - .renderHorizontalGridLines(true) - .title(function(d) { return d.key + ": " + d.value; }) - .brushOn(true) - ; - mv.pcChart = dc.barChart("#player-chart") - .width(700) - .height(130) - .margins({left: 60, right: 18, top: 5, bottom: 30}) - .dimension(pcDim) - .group(pcGroup) - .gap(1) -// .elasticX(true) - .elasticY(true) - .x(d3.scale.linear().domain([pcDim.bottom(1)[0].pc, pcDim.top(1)[0].pc]).nice()) - .renderHorizontalGridLines(true) - .title(function(d) { return d.key + ": " + d.value; }) - .brushOn(true) - ; - mv.blvlChart = dc.barChart("#blvl-chart") - .width(380) - .height(130) - .margins({left: 60, right: 18, top: 5, bottom: 30}) - .dimension(blvlDim) - .group(blvlGroup) - .gap(1) - .elasticY(true) - .x(d3.scale.linear().domain([0, blvlDim.top(1)[0].pcstat.blvl])) - .renderHorizontalGridLines(true) - .title(function(d) { return d.key + ": " + d.value; }) - .brushOn(true) - ; - mv.typeChart = dc.pieChart("#type-chart") - .width(380) - .height(130) - .radius(60) - .dimension(typeDim) - .group(typeGroup) - .colorCalculator(d3.scale.category20c()) - ; - mv.targetChart = dc.pieChart("#target-chart") - .width(380) - .height(130) - .radius(60) - .dimension(targetDim) - .group(targetGroup) - .colorCalculator(d3.scale.category20c()) - ; - mv.targetChart = dc.pieChart("#wpn-chart") - .width(380) - .height(130) - .radius(60) - .dimension(wpnDim) - .group(wpnGroup) - .colorCalculator(d3.scale.category20c()) - ; - mv.defChart = dc.pieChart("#def-chart") - .width(380) - .height(130) - .radius(60) - .dimension(defDim) - .group(defGroup) - .label(function(d) { return defLevelVerbose(d.data.key); }) - .title(function(d) { return defLevelVerbose(d.data.key) + ": " + d.value; }) - .colorAccessor(function(d) { return d.data.key; }) - .colorCalculator(function(k) { switch(k) { - case 0: return "#fd350d"; - case 1: return "#fdae6b"; - case 2: return "#6baed6"; - default: throw "Definition chart: Color access key out of range!"; - }}) - .filter(2) - ; - mv.mapChart = dc.bubbleChart("#map-chart") - .width(700) - .height(500) - .margins({left: 60, right: 18, top: 5, bottom: 30}) - .dimension(mapDim) - .group(mapGroup) - .colorCalculator(d3.scale.category20c()) - /* X */ - .keyAccessor(function(d) { return d.value.e + 1; }) - /* Y */ - .valueAccessor(function(d) { return d.value.j + 1; }) - /* R */ - .radiusValueAccessor(function(d) { return Math.log(d.value.r + 1); }) - .x(d3.scale.log().domain([1, 100000])) - .y(d3.scale.log().domain([1, 300000])) - .elasticX(true) - .elasticY(true) - .renderHorizontalGridLines(true) - .renderVerticalGridLines(true) - .title(function(d) { return "Map " + d.key; }) - .renderTitle(true) - ; - dc.renderAll(); - } -})(); diff --git a/js/mv/chart.js b/js/mv/chart.js new file mode 100644 index 0000000..9e07e2a --- /dev/null +++ b/js/mv/chart.js @@ -0,0 +1,107 @@ +var mv = function(mv) { + mv.charts = {}; + mv.charter = function() { + var charter = {}; + charter.init = function() { + mv.charts.date = bar(monoGroup(wide(dc.barChart("#date-chart")), "date")) + .centerBar(true) + .elasticX(true) + .x(d3.time.scale().domain([mv.heap.date.dim.bottom(1)[0].date, mv.heap.date.dim.top(1)[0].date]).nice(d3.time.hour)) + .xUnits(d3.time.hours) + .xAxisPadding(2) + ; + mv.charts.pc = bar(monoGroup(wide(dc.barChart("#player-chart")), "pc")) + .x(d3.scale.linear().domain([mv.heap.pc.dim.bottom(1)[0].pc, mv.heap.pc.dim.top(1)[0].pc]).nice()) + ; + mv.charts.blvl = bar(monoGroup(thin(dc.barChart("#blvl-chart")), "blvl")) + .x(d3.scale.linear().domain([0, mv.heap.blvl.dim.top(1)[0].pcstat.blvl])) + ; + mv.charts.type = pie(monoGroup(dc.pieChart("#type-chart"), "type")) + ; + mv.charts.target = pie(monoGroup(dc.pieChart("#target-chart"), "target")) + ; + mv.charts.target = pie(monoGroup(dc.pieChart("#wpn-chart"), "wpn")) + ; + mv.charts.def = pie(monoGroup(dc.pieChart("#def-chart"), "def")) + .label(function(d) { return defLevelVerbose(d.data.key); }) + .title(function(d) { return defLevelVerbose(d.data.key) + ": " + d.value; }) + .colorAccessor(function(d) { return d.data.key; }) + .colorCalculator(function(k) { switch(k) { + case 0: return "#fd350d"; + case 1: return "#fdae6b"; + case 2: return "#6baed6"; + default: throw "Definition chart: Color access key out of range!"; + }}) + ; + mv.charts.map = monoGroup(margined(wide(dc.bubbleChart("#map-chart"))), "map") + .height(500) + .colorCalculator(d3.scale.category20c()) + /* X */ + .keyAccessor(function(d) { return d.value.e + 1; }) + /* Y */ + .valueAccessor(function(d) { return d.value.j + 1; }) + /* R */ + .radiusValueAccessor(function(d) { return Math.log(d.value.r + 1); }) + .x(d3.scale.log().domain([1, 100000])) + .y(d3.scale.log().domain([1, 300000])) + .elasticX(true) + .elasticY(true) + .renderHorizontalGridLines(true) + .renderVerticalGridLines(true) + .title(function(d) { return "Map " + d.key; }) + .renderTitle(true) + ; + dc.renderAll(); + } + function defLevelVerbose(level) { + switch (level) { + case 0: return "Undefined"; + case 1: return "Mixed"; + case 2: return "Defined"; + default: console.log(d, d.data); throw "Unknown definedness case (" + d.data.key + "); this shouldn't happen"; + } + } + return charter; + }(); + function wide(chart) { + return chart + .width(700) + ; + } + function thin(chart) { + return chart + .width(380) + ; + } + function short(chart) { + return chart + .height(130) + ; + } + function margined(chart) { + return chart + .margins({left: 60, right: 18, top: 5, bottom: 30}) + } + function monoGroup(chart, name) { + return chart + .dimension(mv.heap[name].dim) + .group(mv.heap[name].group) + ; + } + function bar(chart) { + return margined(short(chart)) + .elasticY(true) + .gap(1) + .renderHorizontalGridLines(true) + .title(function(d) { return d.key + ": " + d.value; }) + .brushOn(true) + ; + } + function pie(chart) { + return thin(short(chart)) + .radius(60) + .colorCalculator(d3.scale.category20c()) + ; + } + return mv; +}(mv || {}); diff --git a/js/mv/heap.js b/js/mv/heap.js new file mode 100644 index 0000000..bd1d583 --- /dev/null +++ b/js/mv/heap.js @@ -0,0 +1,34 @@ +var mv = function(mv) { + mv.heap = function() { + var heap = {}; + heap.init = function() { + function a(p, d) { return { e: p.e + d.e, j: p.j + d.j, r: p.r + 1 }; } + function s(p, d) { return { e: p.e - d.e, j: p.j - d.j, r: p.r - 1 }; } + function z(p, d) { return { e: 0, j: 0, r: 0 }; } + heap.cfdata = crossfilter(mv.parser.records); + heap.all = heap.cfdata.groupAll().reduce(a, s, z); + monoGroup("date", function(d) { return d3.time.hour.round(d.date); }); + monoGroup("pc", function(d) { return d.pc; }); + monoGroup("map", function(d) { return d.map; }).reduce(a, s, z); + monoGroup("blvl", function(d) { return d.pcstat ? d.pcstat.blvl : 0; }); + monoGroup("type", function(d) { return d.type; }); + monoGroup("target", function(d) { return d.target; }); + monoGroup("wpn", function(d) { return d.wpn; }) + /* Debugging group */ + /* + * How well defined a record is. + * 0 -> Record contains undefined data + * 1 -> Record is defined, but undefined records follow and may impede validity of findings + * 2 -> Record and all succeeding records are well defined + */ + monoGroup("def", function(d) { if (d.pcstat == undefined) { return 0; } if (d.date <= mv.parser.fullyDefinedCutoff()) { return 1; } return 2; }); + heap.def.dim.filterExact(2); + } + function monoGroup(name, mapping) { + heap[name] = {}; + return heap[name].group = (heap[name].dim = heap.cfdata.dimension(mapping)).group(); + } + return heap; + }(); + return mv; +}(mv || {}); diff --git a/js/mv/load.js b/js/mv/load.js new file mode 100644 index 0000000..2eca272 --- /dev/null +++ b/js/mv/load.js @@ -0,0 +1,60 @@ +var mv = function(mv) { + mv.loader = function() { + /* Set up handlers for file selector */ + var numfiles = 0; + var filenames = []; + var curfile = 0; + var loader = {}; + loader.onbulkstart = function(fevt) {}; + loader.onloadstart = function(evt) {}; + loader.onprogress = function(evt) {}; + loader.onabort = function(evt) { + alert('File load aborted!'); + }; + loader.onerror = function(evt) { + switch(evt.target.error.code) { + case evt.target.error.NOT_FOUND_ERR: + alert('File Not Found!'); + break; + case evt.target.error.NOT_READABLE_ERR: + alert('File is not readable'); + break; + case evt.target.error.ABORT_ERR: + break; // noop + default: + alert('An error occurred reading this file.'); + }; + }; + loader.numfiles = function() { return numfiles; }; + loader.filenames = function() { return filenames; }; + loader.curfile = function() { return curfile; }; + loader.init = function(each, after) { + document.getElementById('input').addEventListener('change', function(fevt) { + numfiles = fevt.target.files.length; + filenames = Array.prototype.map.call(fevt.target.files, function(d) { return d.name; }); + curfile = 0; + var reader = new FileReader(); + loader.onbulkstart(fevt); + reader.onerror = function() { loader.onerror.apply(null, arguments) }; + reader.onprogress = function() { loader.onprogress.apply(null, arguments) }; + reader.onabort = function() { loader.onabort.apply(null, arguments) }; + reader.onloadstart = function() { loader.onloadstart.apply(null, arguments) }; + reader.onload = function(evt) { + each(reader.result); + ++curfile; + if (curfile == numfiles) { + after(); + } else { + nextFile(); + } + } + function nextFile() { + setTimeout(function(){ console.log("Loading file ", curfile); reader.readAsBinaryString(fevt.target.files[curfile]);}, 0); + } + nextFile(); + }, false); + }; + return loader; + }(); + return mv; +}(mv || {}); diff --git a/js/mv/main.js b/js/mv/main.js new file mode 100644 index 0000000..51d1dae --- /dev/null +++ b/js/mv/main.js @@ -0,0 +1,57 @@ +var mv = function(mv) { + mv.init = function() { + console.log("Initialising"); + var loadbar = progress('loadbar'); + var filesbar = progress('filesbar'); + var lbase = loadbar.label; + loadbar.label = function() { + return lbase() == '100%' ? "Loaded '" + mv.loader.filenames()[mv.loader.curfile()]+ "' - Done!" : "Loading '" + mv.loader.filenames()[mv.loader.curfile()] + "' - " + lbase(); + }; + var fbase = filesbar.label; + filesbar.label = function () { + return fbase() == '100%' ? "Loaded " + mv.loader.numfiles() + " file(s) - Done!" : "Loading file " + mv.loader.curfile() + " of " + mv.loader.numfiles() + " - " + fbase(); + } + loadbar.show(); + filesbar.show(); + mv.loader.onbulkstart = function(fevt) { + loadbar.show(); + filesbar.show(); + }; + mv.loader.onloadstart = function(evt) { + filesbar.update(mv.loader.curfile(), mv.loader.numfiles()); + loadbar.reset(); + }; + mv.loader.onprogress = function(evt) { + if (evt.lengthComputable) { + loadbar.update(evt.loaded, evt.total); + } + }; + mv.loader.init(handleFile, postLoading); + function handleFile(data, curFileNum, numFiles) { + loadbar.complete(); + mv.parser.parseRecords(data); + } + function postLoading() { + filesbar.complete(); + /* TODO: This is still a total mess that doesn't really transition properly */ + setTimeout(function() { + loadbar.hide(); + }, 2000); + mv.heap.init(); + setTimeout(function() { + filesbar.hide(); + d3.select("#mask") + .transition() + .style("opacity", 0) + .remove(); + d3.selectAll(".vis-hide") + .style("display", "inline") + .transition() + .style("opacity", 1) + ; + mv.charter.init(); + }, 2000); + } + }; + return mv; +}(mv || {}); diff --git a/js/mv/parse.js b/js/mv/parse.js new file mode 100644 index 0000000..23a0f79 --- /dev/null +++ b/js/mv/parse.js @@ -0,0 +1,86 @@ +var mv = function(mv) { + mv.parser = function() { + var parser = {}; + var pcstat = {}; + var fullyDefinedCutoff = 0; + parser.records = []; + parser.fullyDefinedCutoff = function() { return fullyDefinedCutoff; }; + parser.parseRecords = function(data) { + var spl = data.split(/\r?\n/); + spl.forEach(function(e, i) { + var d; + d = e.match(/^(\d+\.\d+) PC(\d+) (\d+):(\d+),(\d+) GAINXP (\d+) (\d+) (\w+)/); + if (d) { + var mapSID = parseInt(d[3]); + var ts = new Date(0); + ts.setUTCSeconds(d[1]); + var rec = { + date: ts, + pc: parseInt(d[2]), + map: map.nameByServerID(parseInt(d[3]), ts), + x: parseInt(d[4]), + y: parseInt(d[5]), + e: parseInt(d[6]), + j: parseInt(d[7]), + type: d[8], + pcstat: pcstat[d[2]], + target: 0, + dmg: 0, + wpn: 0 + }; + if (pcstat[d[2]] == undefined && (!fullyDefinedCutoff || ts > fullyDefinedCutoff)) { + fullyDefinedCutoff = ts; + } + /* XXX: Fragile horrible and unstructured, this whole thing needs a rewrite really */ + if (i >= 2 && rec.type == "KILLXP") { + d = spl[i - 1].match(/^(\d+\.\d+) MOB(\d+) DEAD/); + if (d) { + var mID = parseInt(d[2]); + /* There's a massive wealth of data that can be collected from this. Number of assailants, weapons used, the relationships with the assailants... this can't be done with a simple lookbehind. For now, just extract what mob it was, and what the killing weapon used was. */ + d = spl[i - 2].match(/^(\d+\.\d+) PC(\d+) (\d+):(\d+),(\d+) WPNDMG MOB(\d+) (\d+) FOR (\d+) WPN (\d+)/); + if (d) { + softAssert(mID == parseInt(d[6]), "Integrity error: MOB ID mismatch!"); + // softAssert(rec.pc == parseInt(d[2]), "Integrity error: PC ID mismatch!"); + rec.target = parseInt(d[7]); + rec.dmg = parseInt(d[8]); + rec.wpn = parseInt(d[9]); + } + } else { + d = spl[i - 1].match(/^(\d+\.\d+) PC(\d+) (\d+):(\d+),(\d+) GAINXP (\d+) (\d+) (\w+)/); + if (d) { + var clone = parser.records[parser.records.length - 1]; + softAssert(rec.map == clone.map, "Integrity error: MAP ID mismatch!"); + rec.target = clone.target; + rec.dmg = clone.dmg; /* FIXME: Take into account actual assist damage */ + rec.wpn = clone.wpn; + } + } + } + parser.records.push(rec); + return; + } + d = e.match(/^(?:\d+\.\d+) PC(\d+) (?:\d+):(?:\d+),(?:\d+) STAT (\d+) (\d+) (\d+) (\d+) (\d+) (\d+) /); + if (d) { + var s = { + str: parseInt(d[2]), + agi: parseInt(d[3]), + vit: parseInt(d[4]), + int: parseInt(d[5]), + dex: parseInt(d[6]), + luk: parseInt(d[7]) + }; + s.blvl = stat.minLevelForStats(s.str, s.agi, s.vit, s.int, s.dex, s.luk); + pcstat[d[1]] = s; + return; + } + }); + }; + function softAssert(expr, msg) { + if (!expr) { + console.error("SOFTASSERT FAILURE: " + msg); + } + } + return parser; + }(); + return mv; +}(mv || {}); |