summaryrefslogblamecommitdiff
path: root/js/mv.js
blob: b4910ed4add892a2e72a4992bb9704025f656aa6 (plain) (tree)
1
2
3
4
5
6
7
8
9



                                                                  
            



             




                                                









































                                                                              


                               
                     





















                                                                                                                                                                               









                                                                                                                    

                                  
                                


                                                                                    
                                    

                               
                   

                               
                                                       




                               




                               


                                                                                            

























                                                                                                                                                                                                                                                                                          



                                                                                                             
                  






                              

                                                                                 

               

       



                       
                       



                           





                                                                                                
                     

                                               










                                                                                   

                                                                                     






                                                                   


                                                                                                                                                  












                                        
                             




                                             
                 















                                                                                                        
                                             
                 











                                                                                      
                                             
                 










                                                                      
                                             
                 






                                                 
                 






                                              
                 





                                              
                                           
                 














                                                                                  





                                                         
                                              















                                                                           

     
"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();
  }
})();