summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--css/style.css4
-rw-r--r--index.html26
-rw-r--r--js/mv.js332
-rw-r--r--js/mv/chart.js107
-rw-r--r--js/mv/heap.js34
-rw-r--r--js/mv/load.js60
-rw-r--r--js/mv/main.js57
-rw-r--r--js/mv/parse.js86
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;
}
diff --git a/index.html b/index.html
index ea347f2..9a45e27 100644
--- a/index.html
+++ b/index.html
@@ -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 || {});