summaryrefslogtreecommitdiff
path: root/public
diff options
context:
space:
mode:
authorFreeyorp <TheFreeYorp@NOSPAM.G.m.a.i.l.replace>2013-05-13 00:35:11 +1200
committerFreeyorp <TheFreeYorp@NOSPAM.G.m.a.i.l.replace>2013-05-13 00:41:08 +1200
commit5e332bfac3dfe2075dde30568498bc6c42ca27ce (patch)
treecaaa5d48e3f448527e3e62359a6b4453bdf3655f /public
parent2b3bf882d6b53f09e0dddf3f8d4f159832471fb3 (diff)
downloadmanavis-5e332bfac3dfe2075dde30568498bc6c42ca27ce.tar.gz
manavis-5e332bfac3dfe2075dde30568498bc6c42ca27ce.tar.bz2
manavis-5e332bfac3dfe2075dde30568498bc6c42ca27ce.tar.xz
manavis-5e332bfac3dfe2075dde30568498bc6c42ca27ce.zip
Move served files to a public/ directory
Diffstat (limited to 'public')
-rw-r--r--public/css/style.css134
-rw-r--r--public/index.html96
-rw-r--r--public/js/comp/item.js809
-rwxr-xr-xpublic/js/comp/makeitem.sed16
-rwxr-xr-xpublic/js/comp/makemap.sed17
-rwxr-xr-xpublic/js/comp/makemob.sed14
-rw-r--r--public/js/comp/map.js114
-rw-r--r--public/js/comp/mob.js101
-rw-r--r--public/js/comp/stat.js153
m---------public/js/crossfilter0
m---------public/js/d30
m---------public/js/dc0
-rw-r--r--public/js/mv/chart.js131
-rw-r--r--public/js/mv/connect.js156
-rw-r--r--public/js/mv/heap.js63
-rw-r--r--public/js/mv/load.js60
-rw-r--r--public/js/mv/main.js59
-rw-r--r--public/js/mv/parse.js109
-rw-r--r--public/js/util/memoize.js45
-rw-r--r--public/js/util/progress.js37
-rw-r--r--public/js/util/trellis-chart.js140
21 files changed, 2254 insertions, 0 deletions
diff --git a/public/css/style.css b/public/css/style.css
new file mode 100644
index 0000000..2690b1f
--- /dev/null
+++ b/public/css/style.css
@@ -0,0 +1,134 @@
+/* Ensure sane defaults */
+
+body {
+ padding: 0;
+ margin: 0;
+}
+
+/* Top-level layout */
+.side {
+ float: right;
+}
+#main {
+ overflow: hidden;
+}
+
+#mask {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ background-color: #333;
+ opacity: .8;
+ -moz-transition: opacity 1s linear;
+ -o-transition: opacity 1s linear;
+ -webkit-transition: opacity 1s linear;
+ transition: opacity 1s linear;
+ top: 0;
+}
+
+body, #main, #status, .side {
+ min-height: 40px;
+}
+
+body {
+ min-width: 1350px;
+}
+
+#status {
+ width: 100%;
+ font-size: smaller;
+}
+
+/* Columns */
+
+.med {
+ width: 400px;
+}
+
+.thin {
+ width: 250px;
+}
+
+#main, .side {
+ font-size: smaller;
+}
+
+/* Titles */
+h3 {
+ margin: 0.3em 0.8em;
+}
+
+/* Loadinfo panel */
+
+#loadinfo {
+ position: absolute;
+ top: 0px;
+ bottom: 0px;
+ left: 0px;
+ right: 0px;
+ width: 647px;
+ height: 400px;
+ border: 1px grey solid;
+ margin: auto;
+ background-color: #fff;
+ padding: 20px;
+}
+
+/* Hide charts while loadinfo is shown */
+
+.vis-hide {
+ display: none;
+ opacity: 0;
+}
+
+/* Loading bars */
+.progressbar {
+ margin: 10px 0;
+ padding: 3px;
+ border: 1px solid #000;
+ font-size: 14px;
+ clear: both;
+ opacity: 0;
+}
+
+.progressbar.loading {
+ opacity: 1.0;
+}
+
+.progressbar .percent {
+ background-color: #99ccff;
+ height: auto;
+ width: 0;
+ white-space: nowrap;
+}
+
+/* Stat chart */
+
+#stat-chart svg g g.column .border-line {
+ fill: none;
+ stroke: #ccc;
+ opacity: .5;
+ shape-rendering: crispEdges;
+}
+
+/* User list */
+
+#users-status {
+ padding: 0;
+}
+
+#users-status li.user {
+ list-style: none;
+}
+
+/* Utility */
+.fader {
+ -moz-transition: opacity 1s linear;
+ -o-transition: opacity 1s linear;
+ -webkit-transition: opacity 1s linear;
+ transition: opacity 1s linear;
+}
+
+.help {
+ cursor: help;
+}
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..baebbb2
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Manavis</title>
+<link rel="stylesheet" type="text/css" href="css/style.css" />
+<link rel="stylesheet" type="text/css" href="js/dc/test/dc.css" />
+
+<body onload="mv.init();">
+ <div class="side med">
+ <div class="vis-hide">
+ <div id="connect-status">
+ <h3>Users</h3>
+ <ul id="users-status"></ul>
+ </div>
+ <div id="blvl-chart">
+ <h3>Instance breakdown by Character Base Level <a class="reset" style="display: none;" href="javascript:mv.charts.blvl.filterAll();dc.redrawAll();">clear</a></h3>
+ </div>
+ <div id="stat-chart">
+ <h3>Instance breakdown by Stat allocation</h3>
+ </div>
+ </div>
+ </div>
+ <div class="side thin">
+ <div class="vis-hide">
+ <div id="target-chart">
+ <h3>Instance breakdown 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>Instance breakdown by Weapon <a class="reset" style="display: none;" href="javascript:mv.charts.wpn.filterAll();dc.redrawAll();">clear</a></h3>
+ </div>
+ <div id="type-chart">
+ <h3>Instance breakdown by Type <a class="reset" style="display: none;" href="javascript:mv.charts.type.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.charts.def.filterAll();dc.redrawAll();">clear</a></h3>
+ </div>
+ </div>
+ </div>
+ <div id="main">
+<!-- <div id="status">
+ Manavis
+ TODO: Load icons et al for when new records need loading?
+ </div>-->
+ <div class="vis-hide">
+ <div id="map-chart">
+ <h3>Breakdown 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>Instance breakdown 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>Instance breakdown 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 id="mask"><noscript><h1>Javascript is required for this website.</h1></noscript>
+ <div id="loadinfo" class="fader">
+ <h1>Manavis</h1>
+ <input id="connect-option" name="connect" type="checkbox" checked></input>
+ <label for="connect">Connect to server</label>
+ <h3>Select records to load and display</h3>
+ <p>You can load any number of files at once.</p>
+ <input type="file" id="input" name="records[]" multiple />
+ <output id="list"></output>
+ <div id="filesbar" class="progressbar fader">
+ <div class="percent">0%</div>
+ </div>
+ <div id="loadbar" class="progressbar fader">
+ <div class="percent">0%</div>
+ </div>
+ </div>
+ </div>
+</body>
+
+<!-- Libs -->
+<script src="js/util/memoize.js"></script>
+<script src="js/util/progress.js"></script>
+<script src="js/d3/d3.js"></script>
+<script src="js/crossfilter/crossfilter.js"></script>
+<script src="js/dc/dc.js"></script>
+
+<script src="/socket.io/socket.io.js"></script>
+
+<!-- Components -->
+<script src="js/comp/item.js"></script>
+<script src="js/comp/map.js"></script>
+<script src="js/comp/mob.js"></script>
+<script src="js/comp/stat.js"></script><!-- Depends on crossfilter for crossfilter.bisect -->
+
+<!-- Processing -->
+<script src="js/mv/chart.js"></script>
+<script src="js/mv/connect.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>
+<script src="js/util/trellis-chart.js"></script>
diff --git a/public/js/comp/item.js b/public/js/comp/item.js
new file mode 100644
index 0000000..65596be
--- /dev/null
+++ b/public/js/comp/item.js
@@ -0,0 +1,809 @@
+var item = function(){
+ var item = {};
+ var items = {
+ 0:"DEFAULT",
+ 501:"CactusDrink",
+ 502:"CactusPotion",
+ 503:"CasinoCoins",
+ 504:"DecorCandy",
+ 505:"MaggotSlime",
+ 506:"CandyCane",
+ 507:"ScorpionStinger",
+ 508:"XmasCake",
+ 509:"ChocolateBar",
+ 510:"Candy",
+ 511:"SantaHat",
+ 512:"GingerBreadMan",
+ 513:"Cake",
+ 514:"XmasCandyCane",
+ 515:"PurplePresentBox",
+ 516:"BluePresentBox",
+ 517:"RedScorpionStinger",
+ 518:"BugLeg",
+ 519:"CherryCake",
+ 520:"EasterEgg",
+ 521:"Dagger",
+ 522:"SharpKnife",
+ 523:"LeatherShirt",
+ 524:"FancyHat",
+ 525:"MinersHat",
+ 526:"CoinBag",
+ 527:"Milk",
+ 528:"Boots",
+ 529:"IronArrow",
+ 530:"ShortBow",
+ 531:"MinerGloves",
+ 532:"LeatherGloves",
+ 533:"RoastedMaggot",
+ 534:"OrangeCupcake",
+ 535:"RedApple",
+ 536:"ShortSword",
+ 537:"TreasureKey",
+ 538:"GreenPresentBox",
+ 539:"Beer",
+ 540:"EmptyBottle",
+ 541:"BottleOfWater",
+ 542:"BottleOfSand",
+ 543:"StandardHeadband",
+ 544:"SilkHeadband",
+ 545:"ForestBow",
+ 546:"DesertShirt",
+ 547:"Bardiche",
+ 548:"Halberd",
+ 549:"Axe",
+ 550:"BlacksmithsAxe",
+ 551:"AquaHint",
+ 552:"MagentaHint",
+ 553:"YellowHint",
+ 554:"GreenHint",
+ 555:"TealHint",
+ 556:"PurpleHint",
+ 557:"RedHint",
+ 558:"BlueHint",
+ 559:"OrangeHint",
+ 560:"GrayHint",
+ 561:"Sabre",
+ 562:"ChickenLeg",
+ 563:"WinterGloves",
+ 564:"TurtleneckSweater",
+ 565:"PinkPetal",
+ 566:"SmallMushroom",
+ 567:"IronPotion",
+ 568:"ConcentrationPotion",
+ 569:"RawLog",
+ 570:"BoneKnife",
+ 571:"Setzer",
+ 572:"Scimitar",
+ 573:"Falchion",
+ 574:"Scorpion",
+ 575:"DesertBow",
+ 576:"Beheader",
+ 577:"BoneDarts",
+ 578:"SandCutter",
+ 579:"RockKnife",
+ 580:"StaffOfLife",
+ 581:"CrescentRod",
+ 582:"StaffOfFire",
+ 583:"StaffOfIce",
+ 584:"Jackal",
+ 585:"ScarabArmlet",
+ 586:"CottonShorts",
+ 587:"Sword",
+ 594:"Spear",
+ 601:"SteelShield",
+ 602:"WoodenShield",
+ 603:"LeatherShield",
+ 610:"JeansShorts",
+ 611:"WhiteFur",
+ 612:"CaveSnakeLamp",
+ 613:"HardSpike",
+ 614:"PinkAntenna",
+ 615:"PumpkinHelmet",
+ 616:"AxeHat",
+ 617:"PirateHat",
+ 618:"Goggles",
+ 619:"LeatherGoggles",
+ 620:"Circlet",
+ 621:"Eyepatch",
+ 622:"Bandana",
+ 623:"Scythe",
+ 624:"VNeckSweater",
+ 625:"ChainmailShirt",
+ 626:"LightPlatemail",
+ 627:"TopHat",
+ 628:"FunkyHat",
+ 629:"MushHat",
+ 630:"ShroomHat",
+ 631:"DarkCrystal",
+ 632:"CottonSkirt",
+ 633:"ChristmasElfHat",
+ 634:"FaceMask",
+ 635:"SantaCookie",
+ 636:"WarlordHelmet",
+ 637:"KnightsHelmet",
+ 638:"InfantryHelmet",
+ 639:"CrusadeHelmet",
+ 640:"IronOre",
+ 641:"SnakeSkin",
+ 642:"JeansChaps",
+ 643:"WhiteCowboyHat",
+ 644:"BlackCowboyHat",
+ 645:"GoldenPlatemail",
+ 646:"Crown",
+ 647:"DevelopersCap",
+ 648:"CottonTrousers",
+ 649:"WhiteEvokersRobeBlue",
+ 650:"BlackEvokersRobeBlue",
+ 651:"WhiteWizardRobe",
+ 652:"BlackWizardRobe",
+ 653:"ApprenticeRobe",
+ 654:"Cap",
+ 655:"FurBoots",
+ 656:"SerfHat",
+ 657:"Orange",
+ 658:"WarlordPlate",
+ 659:"GoldenWarlordPlate",
+ 660:"CottonCloth",
+ 661:"RedRose",
+ 662:"WhiteRose",
+ 663:"DarkRedRose",
+ 664:"PinkRose",
+ 665:"YellowRose",
+ 666:"BlackRose",
+ 667:"OrangeRose",
+ 668:"BlueRose",
+ 669:"YellowTulip",
+ 670:"PurpleTulip",
+ 671:"RedTulip",
+ 672:"WhiteTulip",
+ 673:"PinkTulip",
+ 674:"OrangeTulip",
+ 675:"GraduationCap",
+ 676:"Steak",
+ 677:"HeartNecklace",
+ 678:"NohMask",
+ 679:"DemonMask",
+ 680:"MauveHerb",
+ 681:"CobaltHerb",
+ 682:"GambogeHerb",
+ 683:"AlizarinHerb",
+ 684:"TinyHealingPotion",
+ 685:"SmallHealingPotion",
+ 686:"MediumHealingPotion",
+ 687:"LargeHealingPotion",
+ 688:"TankTop",
+ 689:"ShortTankTop",
+ 690:"RedDye",
+ 691:"GreenDye",
+ 692:"DarkBlueDye",
+ 693:"YellowDye",
+ 694:"LightBlueDye",
+ 695:"PinkDye",
+ 696:"BlackDye",
+ 697:"OrangeDye",
+ 698:"PurpleDye",
+ 699:"DarkGreenDye",
+ 700:"Pearl",
+ 701:"PileOfAsh",
+ 702:"WeddingRing",
+ 703:"SulphurPowder",
+ 704:"IronPowder",
+ 705:"ManaPotion",
+ 706:"GoldenScorpionStinger",
+ 707:"MonsterOilPotion",
+ 708:"LeatherPatch",
+ 709:"BlackScorpionStinger",
+ 710:"SnakeTongue",
+ 711:"MountainSnakeTongue",
+ 712:"GrassSnakeTongue",
+ 713:"CaveSnakeTongue",
+ 714:"SnakeEgg",
+ 715:"MountainSnakeEgg",
+ 716:"GrassSnakeEgg",
+ 717:"CaveSnakeEgg",
+ 718:"SilkCocoon",
+ 719:"GreenApple",
+ 720:"SilkRobe",
+ 721:"HighPriestCrown",
+ 722:"MonsterSkullHelmet",
+ 723:"DesertHat",
+ 724:"CottonHeadband",
+ 725:"GMCap",
+ 726:"GMRobe",
+ 727:"Iten",
+ 728:"MoubooFigurine",
+ 729:"WarpedLog",
+ 730:"Lifestone",
+ 731:"AssassinPants",
+ 732:"DruidTreeBranch",
+ 733:"PurificationPotion",
+ 734:"BlackBoots",
+ 735:"CottonBoots",
+ 736:"WhiteCake",
+ 737:"ChocolateCake",
+ 738:"OrangeCake",
+ 739:"AppleCake",
+ 740:"Root",
+ 741:"CottonGloves",
+ 742:"FourLeafClover",
+ 743:"Acorn",
+ 744:"DilutedConcentrationPotion",
+ 745:"DarkConcentrationPotion",
+ 746:"MopoxCurePotion",
+ 747:"LacedChocolateCake",
+ 748:"LacedOrangeCupcake",
+ 749:"Towel",
+ 750:"SlowPoisonPotion",
+ 751:"PinkieHat",
+ 752:"FluffyHat",
+ 753:"BatWing",
+ 754:"BatTeeth",
+ 755:"AssassinShirt",
+ 756:"AssassinGloves",
+ 757:"AssassinBoots",
+ 758:"WoodenStaff",
+ 762:"TerraniteArrow",
+ 763:"TerraniteOre",
+ 766:"TerraniteHelmet",
+ 767:"TerraniteChestArmor",
+ 768:"TerraniteLegs",
+ 769:"GuyFawkesMask",
+ 770:"FairyHat",
+ 771:"Miniskirt",
+ 772:"WispPowder",
+ 773:"SpectrePowder",
+ 774:"PoltergeistPowder",
+ 775:"Bone",
+ 776:"Skull",
+ 777:"RottenRags",
+ 778:"DiseasedHeart",
+ 779:"UndeadEar",
+ 780:"UndeadEye",
+ 782:"ForestArmor",
+ 783:"PlatynaRedDress",
+ 784:"ZombieNachos",
+ 785:"LadyFingers",
+ 786:"JellAhh",
+ 787:"Snapple",
+ 788:"BeetleJuice",
+ 789:"GutBuster",
+ 790:"BloodWine",
+ 791:"YetiSkinShirt",
+ 792:"BromenalBoots",
+ 793:"BromenalChest",
+ 794:"BromenalGloves",
+ 795:"BromenalHelmet",
+ 796:"BromenalLegs",
+ 797:"BromenalShield",
+ 798:"SorcererRobeRed",
+ 799:"MylarinDust",
+ 800:"BowlerHatBrown",
+ 801:"PinkieHelmet",
+ 802:"EasterBasket",
+ 803:"GrassLiner",
+ 804:"JellyBeans",
+ 805:"ChocolateMouboo",
+ 806:"ReedBundle",
+ 807:"GrassSeed",
+ 808:"HitchhikersTowel",
+ 809:"WhiteHitchhikersTowel",
+ 810:"RedHitchhikersTowel",
+ 811:"GreenHitchhikersTowel",
+ 812:"BlueHitchhikersTowel",
+ 813:"YellowHitchhikersTowel",
+ 814:"PurpleHitchhikersTowel",
+ 815:"OrangeHitchhikersTowel",
+ 816:"PinkHitchhikersTowel",
+ 817:"TealHitchhikersTowel",
+ 818:"LimeHitchhikersTowel",
+ 819:"DiamondPowder",
+ 820:"RubyPowder",
+ 821:"EmeraldPowder",
+ 822:"SapphirePowder",
+ 823:"TopazPowder",
+ 824:"AmethystPowder",
+ 825:"TinyManaElixir",
+ 826:"SmallManaElixir",
+ 827:"MediumManaElixir",
+ 828:"LargeManaElixir",
+ 829:"CrozeniteFourLeafAmulet",
+ 830:"BromenalFourLeafAmulet",
+ 831:"SilverFourLeafAmulet",
+ 832:"GoldenFourLeafAmulet",
+ 833:"BrokenFourLeafAmulet",
+ 834:"BrokenDoll",
+ 835:"HyvernStinger",
+ 836:"GrubSlime",
+ 838:"CranberryLollipop",
+ 839:"GrapeLollipop",
+ 840:"OrangeLollipop",
+ 841:"RedDottedWrap",
+ 842:"YellowDottedWrap",
+ 843:"BlueDottedWrap",
+ 844:"PurpleStripedWrap",
+ 845:"RedGoldenStripedWrap",
+ 846:"GreenRedStripedWrap",
+ 847:"PlushMouboo",
+ 848:"Earmuffs",
+ 849:"OpenPresentBox",
+ 850:"ClosedChristmasBox",
+ 851:"StickReinboo",
+ 852:"LeatherBall",
+ 853:"Doll",
+ 854:"ElfNightcap",
+ 855:"Sunglasses",
+ 856:"KnitCap",
+ 857:"LeatherTrousers",
+ 858:"WolvernTooth",
+ 859:"WolvernPelt",
+ 860:"SquirrelPelt",
+ 861:"WhiteBellTuber",
+ 862:"IcedWater",
+ 863:"SilverMirror",
+ 864:"BookPage",
+ 865:"Grimoire",
+ 866:"LeatherSuitcase",
+ 867:"IceGladius",
+ 868:"SilkGloves",
+ 869:"Antlers",
+ 870:"FineDress",
+ 871:"SealedSoul",
+ 872:"LockPicks",
+ 873:"LazuriteShard",
+ 874:"LazuriteCrystal",
+ 875:"HeartOfLazurite",
+ 876:"WarlordBoots",
+ 877:"BullHelmet",
+ 878:"BansheeBow",
+ 879:"HeartOfIsis",
+ 880:"LazuriteRobe",
+ 881:"RaggedShorts",
+ 882:"RedEggshellHat",
+ 883:"BlueEggshellHat",
+ 884:"YellowEggshellHat",
+ 885:"GreenEggshellHat",
+ 886:"OrangeEggshellHat",
+ 887:"DarkEggshellHat",
+ 888:"MagicGMTopHat",
+ 889:"MurdererCrown",
+ 890:"BeanieCopter",
+ 1198:"JackOSoul",
+ 1199:"Arrow",
+ 1200:"Bow",
+ 1201:"Knife",
+ 1202:"CottonShirt",
+ 1203:"RangerHat",
+ 1204:"AntlerHat",
+ 1205:"ChristmasTreeHat",
+ 1206:"SantaBeardHat",
+ 1207:"RedChristmasStocking",
+ 1208:"RedEasterEgg",
+ 1209:"GreenEasterEgg",
+ 1210:"BlueEasterEgg",
+ 1211:"YellowEasterEgg",
+ 1212:"PinkEasterEgg",
+ 1213:"TealEasterEgg",
+ 1214:"BunnyEars",
+ 1215:"ToySabre",
+ 1216:"MoubooHead",
+ 1217:"CatEars",
+ 1218:"PaperBag",
+ 1219:"MoubootaurHead",
+ 1220:"BunchOfParsley",
+ 1221:"SkullMask",
+ 1228:"LightCrystal",
+ 1229:"CaramelApple",
+ 1230:"LollipopColor1",
+ 1231:"LollipopColor2",
+ 1232:"LollipopColor3",
+ 1233:"FakeFangs",
+ 1234:"RedOrnament",
+ 1235:"YellowOrnament",
+ 1236:"GreenOrnament",
+ 1237:"AquaOrnament",
+ 1238:"BlueOrnament",
+ 1239:"MagentaOrnament",
+ 1240:"SantaSnowGlobe",
+ 1241:"SnowmanSnowGlobe",
+ 1242:"SnowGoggles",
+ 1244:"DarkTalisman",
+ 1245:"BentNeedle",
+ 1246:"DarkEasterEgg",
+ 1247:"HeartGlasses",
+ 1248:"Blueberries",
+ 1249:"StrangeCoin",
+ 1250:"Pear",
+ 1251:"Plum",
+ 1252:"Cherry",
+ 1253:"GoldenDeliciousApple",
+ 1254:"DarkPetal",
+ 1255:"WhiteRabbitEars",
+ 1256:"EggshellHat",
+ 1257:"FlawedLens",
+ 1258:"Honey",
+ 1276:"OperaMask",
+ 1277:"JesterMask",
+ 1278:"WitchHat",
+ 1279:"GoblinMask",
+ 1280:"Scissors",
+ 1281:"ShockSweet",
+ 1282:"BoneArrows",
+ 2050:"RedCottonShirt",
+ 2051:"GreenCottonShirt",
+ 2052:"DarkBlueCottonShirt",
+ 2053:"YellowCottonShirt",
+ 2054:"LightBlueCottonShirt",
+ 2055:"PinkCottonShirt",
+ 2056:"BlackCottonShirt",
+ 2057:"OrangeCottonShirt",
+ 2058:"PurpleCottonShirt",
+ 2059:"DarkGreenCottonShirt",
+ 2060:"RedVNeckSweater",
+ 2061:"GreenVNeckSweater",
+ 2062:"DarkBlueVNeckSweater",
+ 2063:"YellowVNeckSweater",
+ 2064:"LightBlueVNeckSweater",
+ 2065:"PinkVNeckSweater",
+ 2066:"BlackVNeckSweater",
+ 2067:"OrangeVNeckSweater",
+ 2068:"PurpleVNeckSweater",
+ 2069:"DarkGreenVNeckSweater",
+ 2070:"RedTurtleneckSweater",
+ 2071:"GreenTurtleneckSweater",
+ 2072:"DarkBlueTurtleneckSweater",
+ 2073:"YellowTurtleneckSweater",
+ 2074:"LightBlueTurtleneckSweater",
+ 2075:"PinkTurtleneckSweater",
+ 2076:"BlackTurtleneckSweater",
+ 2077:"OrangeTurtleneckSweater",
+ 2078:"PurpleTurtleneckSweater",
+ 2079:"DarkGreenTurtleneckSweater",
+ 2080:"RedSilkRobe",
+ 2081:"GreenSilkRobe",
+ 2082:"DarkBlueSilkRobe",
+ 2083:"YellowSilkRobe",
+ 2084:"LightBlueSilkRobe",
+ 2085:"PinkSilkRobe",
+ 2086:"BlackSilkRobe",
+ 2087:"OrangeSilkRobe",
+ 2088:"PurpleSilkRobe",
+ 2089:"DarkGreenSilkRobe",
+ 2090:"RedTankTop",
+ 2091:"GreenTankTop",
+ 2092:"DarkBlueTankTop",
+ 2093:"YellowTankTop",
+ 2094:"LightBlueTankTop",
+ 2095:"PinkTankTop",
+ 2096:"BlackTankTop",
+ 2097:"OrangeTankTop",
+ 2098:"PurpleTankTop",
+ 2099:"DarkGreenTankTop",
+ 2100:"RedCottonSkirt",
+ 2101:"GreenCottonSkirt",
+ 2102:"DarkBlueCottonSkirt",
+ 2103:"YellowCottonSkirt",
+ 2104:"LightBlueCottonSkirt",
+ 2105:"PinkCottonSkirt",
+ 2106:"BlackCottonSkirt",
+ 2107:"OrangeCottonSkirt",
+ 2108:"PurpleCottonSkirt",
+ 2109:"DarkGreenCottonSkirt",
+ 2110:"RedCottonShorts",
+ 2111:"GreenCottonShorts",
+ 2112:"DarkBlueCottonShorts",
+ 2113:"YellowCottonShorts",
+ 2114:"LightBlueCottonShorts",
+ 2115:"PinkCottonShorts",
+ 2116:"BlackCottonShorts",
+ 2117:"OrangeCottonShorts",
+ 2118:"PurpleCottonShorts",
+ 2119:"DarkGreenCottonShorts",
+ 2120:"RedShortTankTop",
+ 2121:"GreenShortTankTop",
+ 2122:"DarkBlueShortTankTop",
+ 2123:"YellowShortTankTop",
+ 2124:"LightBlueShortTankTop",
+ 2125:"PinkShortTankTop",
+ 2126:"BlackShortTankTop",
+ 2127:"OrangeShortTankTop",
+ 2128:"PurpleShortTankTop",
+ 2129:"DarkGreenShortTankTop",
+ 2130:"RedDesertHat",
+ 2131:"GreenDesertHat",
+ 2132:"DarkBlueDesertHat",
+ 2133:"YellowDesertHat",
+ 2134:"LightBlueDesertHat",
+ 2135:"PinkDesertHat",
+ 2136:"BlackDesertHat",
+ 2137:"OrangeDesertHat",
+ 2138:"PurpleDesertHat",
+ 2139:"DarkGreenDesertHat",
+ 2140:"RedCottonHeadband",
+ 2141:"GreenCottonHeadband",
+ 2142:"DarkBlueCottonHeadband",
+ 2143:"YellowCottonHeadband",
+ 2144:"LightBlueCottonHeadband",
+ 2145:"PinkCottonHeadband",
+ 2146:"BlackCottonHeadband",
+ 2147:"OrangeCottonHeadband",
+ 2148:"PurpleCottonHeadband",
+ 2149:"DarkGreenCottonHeadband",
+ 2150:"RedCottonBoots",
+ 2151:"GreenCottonBoots",
+ 2152:"DarkBlueCottonBoots",
+ 2153:"YellowCottonBoots",
+ 2154:"LightBlueCottonBoots",
+ 2155:"PinkCottonBoots",
+ 2156:"BlackCottonBoots",
+ 2157:"OrangeCottonBoots",
+ 2158:"PurpleCottonBoots",
+ 2159:"DarkGreenCottonBoots",
+ 2160:"RedCottonGloves",
+ 2161:"GreenCottonGloves",
+ 2162:"DarkBlueCottonGloves",
+ 2163:"YellowCottonGloves",
+ 2164:"LightBlueCottonGloves",
+ 2165:"PinkCottonGloves",
+ 2166:"BlackCottonGloves",
+ 2167:"OrangeCottonGloves",
+ 2168:"PurpleCottonGloves",
+ 2169:"DarkGreenCottonGloves",
+ 2170:"RedMiniskirt",
+ 2171:"GreenMiniskirt",
+ 2172:"DarkBlueMiniskirt",
+ 2173:"YellowMiniskirt",
+ 2174:"LightBlueMiniskirt",
+ 2175:"PinkMiniskirt",
+ 2176:"BlackMiniskirt",
+ 2177:"OrangeMiniskirt",
+ 2178:"PurpleMiniskirt",
+ 2179:"DarkGreenMiniskirt",
+ 2180:"RedCottonTrousers",
+ 2181:"GreenCottonTrousers",
+ 2182:"DarkBlueCottonTrousers",
+ 2183:"YellowCottonTrousers",
+ 2184:"LightBlueCottonTrousers",
+ 2185:"PinkCottonTrousers",
+ 2186:"BlackCottonTrousers",
+ 2187:"OrangeCottonTrousers",
+ 2188:"PurpleCottonTrousers",
+ 2189:"DarkGreenCottonTrousers",
+ 2190:"RedRabbitEars",
+ 2191:"GreenRabbitEars",
+ 2192:"DarkBlueRabbitEars",
+ 2193:"YellowRabbitEars",
+ 2194:"LightBlueRabbitEars",
+ 2195:"PinkRabbitEars",
+ 2196:"BlackRabbitEars",
+ 2197:"OrangeRabbitEars",
+ 2198:"PurpleRabbitEars",
+ 2199:"DarkGreenRabbitEars",
+ 2200:"RedWizardHat",
+ 2201:"GreenWizardHat",
+ 2202:"DarkBlueWizardHat",
+ 2203:"YellowWizardHat",
+ 2204:"LightBlueWizardHat",
+ 2205:"PinkWizardHat",
+ 2206:"BlackWizardHat",
+ 2207:"OrangeWizardHat",
+ 2208:"PurpleWizardHat",
+ 2209:"DarkGreenWizardHat",
+ 2210:"RedBowlerHat",
+ 2211:"GreenBowlerHat",
+ 2212:"DarkBlueBowlerHat",
+ 2213:"YellowBowlerHat",
+ 2214:"LightBlueBowlerHat",
+ 2215:"PinkBowlerHat",
+ 2216:"BlackBowlerHat",
+ 2217:"OrangeBowlerHat",
+ 2218:"PurpleBowlerHat",
+ 2219:"DarkGreenBowlerHat",
+ 2220:"RedSorcererRobeRed",
+ 2221:"GreenSorcererRobeRed",
+ 2222:"DarkBlueSorcererRobeRed",
+ 2223:"YellowSorcererRobeRed",
+ 2224:"LightBlueSorcererRobeRed",
+ 2225:"PinkSorcererRobeRed",
+ 2226:"BlackSorcererRobeRed",
+ 2227:"OrangeSorcererRobeRed",
+ 2228:"PurpleSorcererRobeRed",
+ 2229:"DarkGreenSorcererRobeRed",
+ 2230:"RedBowlerHatBrown",
+ 2231:"GreenBowlerHatBrown",
+ 2232:"DarkBlueBowlerHatBrown",
+ 2233:"YellowBowlerHatBrown",
+ 2234:"LightBlueBowlerHatBrown",
+ 2235:"PinkBowlerHatBrown",
+ 2236:"BlackBowlerHatBrown",
+ 2237:"OrangeBowlerHatBrown",
+ 2238:"PurpleBowlerHatBrown",
+ 2239:"DarkGreenBowlerHatBrown",
+ 2240:"FineRedDress",
+ 2241:"FineGreenDress",
+ 2242:"FineDarkBlueDress",
+ 2243:"FineYellowDress",
+ 2244:"FineLightBlueDress",
+ 2245:"FinePinkDress",
+ 2246:"FineBlackDress",
+ 2247:"FineOrangeDress",
+ 2248:"FinePurpleDress",
+ 2249:"FineDarkGreenDress",
+ 2250:"RedCottonCloth",
+ 2251:"GreenCottonCloth",
+ 2252:"DarkBlueCottonCloth",
+ 2253:"YellowCottonCloth",
+ 2254:"LightBlueCottonCloth",
+ 2255:"PinkCottonCloth",
+ 2256:"BlackCottonCloth",
+ 2257:"OrangeCottonCloth",
+ 2258:"PurpleCottonCloth",
+ 2259:"DarkGreenCottonCloth",
+ 3000:"JackOLantern",
+ 3001:"RubberBat",
+ 3002:"RealisticBrain",
+ 3003:"JarofBlood",
+ 3004:"Tongue",
+ 3006:"TonoriDelight",
+ 3007:"Marshmallow",
+ 3009:"JellySkull",
+ 3010:"CandyPumpkin",
+ 3011:"PumpkinSeeds",
+ 4000:"AngryScorpionStinger",
+ 4001:"Coal",
+ 4002:"Diamond",
+ 4003:"Ruby",
+ 4004:"Emerald",
+ 4005:"Sapphire",
+ 4006:"Topaz",
+ 4007:"Amethyst",
+ 4008:"DiamondRing",
+ 4009:"RubyRing",
+ 4010:"EmeraldRing",
+ 4011:"SapphireRing",
+ 4012:"TopazRing",
+ 4013:"AmethystRing",
+ 4014:"SimpleRing",
+ 4015:"IronIngot",
+ 4016:"BanditHood",
+ 4017:"RedPowder",
+ 4018:"YellowPowder",
+ 4019:"BluePowder",
+ 4020:"CandleHelmet",
+ 4021:"YellowPresentBox",
+ 4022:"WhitePresentBox",
+ 4023:"AnimalBones",
+ 4024:"FrozenYetiTear",
+ 4025:"YetiClaw",
+ 4026:"IceCube",
+ 4027:"YetiMask",
+ 4028:"WizardHat",
+ 4029:"GrimaceOfDementia",
+ 4030:"BowlerHat",
+ 4031:"Monocle",
+ 4032:"PanHat",
+ 4033:"ChefHat",
+ 4034:"BlackPearl",
+ 4035:"PickledBeets",
+ 4036:"RoastedAcorn",
+ 4037:"WhiteBlanket",
+ 4038:"WhiteSaddleRug",
+ 4039:"RedSaddleRug",
+ 4040:"RawTalisman",
+ 4041:"FlightTalisman",
+ 4042:"RedNose",
+ 5000:"RedSorcererRobeGreen",
+ 5001:"GreenSorcererRobeGreen",
+ 5002:"DarkBlueSorcererRobeGreen",
+ 5003:"YellowSorcererRobeGreen",
+ 5004:"LightBlueSorcererRobeGreen",
+ 5005:"PinkSorcererRobeGreen",
+ 5006:"BlackSorcererRobeGreen",
+ 5007:"OrangeSorcererRobeGreen",
+ 5008:"PurpleSorcererRobeGreen",
+ 5009:"DarkGreenSorcererRobeGreen",
+ 5010:"SorcererRobeGreen",
+ 5011:"RedSorcererRobeDarkBlue",
+ 5012:"GreenSorcererRobeDarkBlue",
+ 5013:"DarkBlueSorcererRobeDarkBlue",
+ 5014:"YellowSorcererRobeDarkBlue",
+ 5015:"LightBlueSorcererRobeDarkBlue",
+ 5016:"PinkSorcererRobeDarkBlue",
+ 5017:"BlackSorcererRobeDarkBlue",
+ 5018:"OrangeSorcererRobeDarkBlue",
+ 5019:"PurpleSorcererRobeDarkBlue",
+ 5020:"DarkGreenSorcererRobeDarkBlue",
+ 5021:"SorcererRobeDarkBlue",
+ 5022:"RedSorcererRobeYellow",
+ 5023:"GreenSorcererRobeYellow",
+ 5024:"DarkBlueSorcererRobeYellow",
+ 5025:"YellowSorcererRobeYellow",
+ 5026:"LightBlueSorcererRobeYellow",
+ 5027:"PinkSorcererRobeYellow",
+ 5028:"BlackSorcererRobeYellow",
+ 5029:"OrangeSorcererRobeYellow",
+ 5030:"PurpleSorcererRobeYellow",
+ 5031:"DarkGreenSorcererRobeYellow",
+ 5032:"SorcererRobeYellow",
+ 5033:"RedSorcererRobeLightBlue",
+ 5034:"GreenSorcererRobeLightBlue",
+ 5035:"DarkBlueSorcererRobeLightBlue",
+ 5036:"YellowSorcererRobeLightBlue",
+ 5037:"LightBlueSorcererRobeLightBlue",
+ 5038:"PinkSorcererRobeLightBlue",
+ 5039:"BlackSorcererRobeLightBlue",
+ 5040:"OrangeSorcererRobeLightBlue",
+ 5041:"PurpleSorcererRobeLightBlue",
+ 5042:"DarkGreenSorcererRobeLightBlue",
+ 5043:"SorcererRobeLightBlue",
+ 5044:"RedSorcererRobePink",
+ 5045:"GreenSorcererRobePink",
+ 5046:"DarkBlueSorcererRobePink",
+ 5047:"YellowSorcererRobePink",
+ 5048:"LightBlueSorcererRobePink",
+ 5049:"PinkSorcererRobePink",
+ 5050:"BlackSorcererRobePink",
+ 5051:"OrangeSorcererRobePink",
+ 5052:"PurpleSorcererRobePink",
+ 5053:"DarkGreenSorcererRobePink",
+ 5054:"SorcererRobePink",
+ 5055:"RedSorcererRobeBlack",
+ 5056:"GreenSorcererRobeBlack",
+ 5057:"DarkBlueSorcererRobeBlack",
+ 5058:"YellowSorcererRobeBlack",
+ 5059:"LightBlueSorcererRobeBlack",
+ 5060:"PinkSorcererRobeBlack",
+ 5061:"BlackSorcererRobeBlack",
+ 5062:"OrangeSorcererRobeBlack",
+ 5063:"PurpleSorcererRobeBlack",
+ 5064:"DarkGreenSorcererRobeBlack",
+ 5065:"SorcererRobeBlack",
+ 5066:"RedSorcererRobeOrange",
+ 5067:"GreenSorcererRobeOrange",
+ 5068:"DarkBlueSorcererRobeOrange",
+ 5069:"YellowSorcererRobeOrange",
+ 5070:"LightBlueSorcererRobeOrange",
+ 5071:"PinkSorcererRobeOrange",
+ 5072:"BlackSorcererRobeOrange",
+ 5073:"OrangeSorcererRobeOrange",
+ 5074:"PurpleSorcererRobeOrange",
+ 5075:"DarkGreenSorcererRobeOrange",
+ 5076:"SorcererRobeOrange",
+ 5077:"RedSorcererRobePurple",
+ 5078:"GreenSorcererRobePurple",
+ 5079:"DarkBlueSorcererRobePurple",
+ 5080:"YellowSorcererRobePurple",
+ 5081:"LightBlueSorcererRobePurple",
+ 5082:"PinkSorcererRobePurple",
+ 5083:"BlackSorcererRobePurple",
+ 5084:"OrangeSorcererRobePurple",
+ 5085:"PurpleSorcererRobePurple",
+ 5086:"DarkGreenSorcererRobePurple",
+ 5087:"SorcererRobePurple",
+ 5088:"RedSorcererRobeDarkGreen",
+ 5089:"GreenSorcererRobeDarkGreen",
+ 5090:"DarkBlueSorcererRobeDarkGreen",
+ 5091:"YellowSorcererRobeDarkGreen",
+ 5092:"LightBlueSorcererRobeDarkGreen",
+ 5093:"PinkSorcererRobeDarkGreen",
+ 5094:"BlackSorcererRobeDarkGreen",
+ 5095:"OrangeSorcererRobeDarkGreen",
+ 5096:"PurpleSorcererRobeDarkGreen",
+ 5097:"DarkGreenSorcererRobeDarkGreen",
+ 5098:"SorcererRobeDarkGreen",
+ 5099:"RedSorcererRobeWhite",
+ 5100:"GreenSorcererRobeWhite",
+ 5101:"DarkBlueSorcererRobeWhite",
+ 5102:"YellowSorcererRobeWhite",
+ 5103:"LightBlueSorcererRobeWhite",
+ 5104:"PinkSorcererRobeWhite",
+ 5105:"BlackSorcererRobeWhite",
+ 5106:"OrangeSorcererRobeWhite",
+ 5107:"PurpleSorcererRobeWhite",
+ 5108:"DarkGreenSorcererRobeWhite",
+ 5109:"SorcererRobeWhite",
+ };
+ item.nameByServerID = function(serverID) {
+ return serverID in items ? items[serverID] : "UNDEFINED";
+ }
+ return item;
+}();
diff --git a/public/js/comp/makeitem.sed b/public/js/comp/makeitem.sed
new file mode 100755
index 0000000..45e5a91
--- /dev/null
+++ b/public/js/comp/makeitem.sed
@@ -0,0 +1,16 @@
+#!/bin/sed -nf
+# Usage: ./makeitem.sed < item.in > item.js
+1i\
+var item = function(){\
+ var item = {};\
+ var items = {
+# /(?<!,0)[\t ]\+,[^,]\+,[^,]\+$/ {
+s/^\([0-9]\+\),[\t ]\+\([^,]\+\).*/ \1:"\2",/p
+# }
+$i\
+ };\
+ item.nameByServerID = function(serverID) {\
+ return serverID in items ? items[serverID] : "UNDEFINED";\
+ }\
+ return item;\
+}();
diff --git a/public/js/comp/makemap.sed b/public/js/comp/makemap.sed
new file mode 100755
index 0000000..561530b
--- /dev/null
+++ b/public/js/comp/makemap.sed
@@ -0,0 +1,17 @@
+#!/bin/sed -nf
+# Usage: ./makemap.sed < map.in > map.js
+1i\
+var map = function(){\
+ var map = {};\
+ var maps = {
+/^Loading Maps/,/^Maps Loaded/ {
+ s/^Loading Maps \[\([0-9]\+\)\/[0-9]\+\]: data\\\(.*\)\.gat/ "\1": "\2",/p;
+}
+$i\
+ };\
+ map.nameByServerID = function(serverID, date) {\
+ /* TODO: Merged output format suitable for converting records running under different data */\
+ return maps[serverID];\
+ }\
+ return map;\
+}();
diff --git a/public/js/comp/makemob.sed b/public/js/comp/makemob.sed
new file mode 100755
index 0000000..c934a4e
--- /dev/null
+++ b/public/js/comp/makemob.sed
@@ -0,0 +1,14 @@
+#!/bin/sed -nf
+# Usage: ./makemob.sed < mob.in > mob.js
+1i\
+var mob = function(){\
+ var mob = {};\
+ var mobs = {
+s/^\([0-9]\+\),[\t ]\+\([^\t ]\+\),.*/ \1:"\2",/p
+$i\
+ };\
+ mob.nameByServerID = function(serverID) {\
+ return serverID in mobs ? mobs[serverID] : "UNDEFINED";\
+ }\
+ return mob;\
+}();
diff --git a/public/js/comp/map.js b/public/js/comp/map.js
new file mode 100644
index 0000000..d845031
--- /dev/null
+++ b/public/js/comp/map.js
@@ -0,0 +1,114 @@
+var map = function(){
+ var map = {};
+ var maps = {
+ "0": "001-1",
+ "1": "001-2",
+ "2": "001-3",
+ "3": "002-1",
+ "4": "002-3",
+ "5": "002-4",
+ "6": "003-1",
+ "7": "003-2",
+ "8": "004-1",
+ "9": "004-2",
+ "10": "005-1",
+ "11": "005-3",
+ "12": "005-4",
+ "13": "006-1",
+ "14": "006-3",
+ "15": "007-1",
+ "16": "008-1",
+ "17": "009-1",
+ "18": "009-2",
+ "19": "009-3",
+ "20": "009-4",
+ "21": "009-5",
+ "22": "009-6",
+ "23": "010-1",
+ "24": "010-2",
+ "25": "011-1",
+ "26": "011-3",
+ "27": "011-4",
+ "28": "011-6",
+ "29": "012-1",
+ "30": "012-3",
+ "31": "012-4",
+ "32": "013-1",
+ "33": "013-2",
+ "34": "013-3",
+ "35": "014-1",
+ "36": "014-3",
+ "37": "015-1",
+ "38": "015-3",
+ "39": "016-1",
+ "40": "017-1",
+ "41": "017-2",
+ "42": "017-3",
+ "43": "017-4",
+ "44": "017-9",
+ "45": "018-1",
+ "46": "018-2",
+ "47": "018-3",
+ "48": "019-1",
+ "49": "019-3",
+ "50": "019-4",
+ "51": "020-1",
+ "52": "020-2",
+ "53": "020-3",
+ "54": "021-1",
+ "55": "021-2",
+ "56": "022-1",
+ "57": "024-1",
+ "58": "024-2",
+ "59": "024-3",
+ "60": "024-4",
+ "61": "025-1",
+ "62": "025-3",
+ "63": "025-4",
+ "64": "026-1",
+ "65": "027-1",
+ "66": "027-2",
+ "67": "027-3",
+ "68": "027-4",
+ "69": "028-1",
+ "70": "028-3",
+ "71": "029-1",
+ "72": "029-3",
+ "73": "030-1",
+ "74": "030-2",
+ "75": "031-1",
+ "76": "031-2",
+ "77": "031-3",
+ "78": "031-4",
+ "79": "032-1",
+ "80": "032-3",
+ "81": "033-1",
+ "82": "034-1",
+ "83": "041-1",
+ "84": "042-1",
+ "85": "042-2",
+ "86": "044-1",
+ "87": "044-3",
+ "88": "045-1",
+ "89": "046-1",
+ "90": "046-3",
+ "91": "047-1",
+ "92": "048-1",
+ "93": "048-2",
+ "94": "051-1",
+ "95": "051-3",
+ "96": "052-1",
+ "97": "052-2",
+ "98": "055-1",
+ "99": "055-3",
+ "100": "056-1",
+ "101": "056-2",
+ "102": "057-1",
+ "103": "botcheck",
+ };
+ map.nameByServerID = function(serverID, date) {
+ /* TODO: Merged output format suitable for converting records running under different data */
+ return maps[serverID];
+ }
+ return map;
+}();
diff --git a/public/js/comp/mob.js b/public/js/comp/mob.js
new file mode 100644
index 0000000..022494e
--- /dev/null
+++ b/public/js/comp/mob.js
@@ -0,0 +1,101 @@
+var mob = function(){
+ var mob = {};
+ var mobs = {
+ 1002:"Maggot",
+ 1003:"Scorpion",
+ 1004:"RedScorpion",
+ 1005:"GreenSlime",
+ 1006:"GiantMaggot",
+ 1007:"YellowSlime",
+ 1008:"RedSlime",
+ 1009:"BlackScorpion",
+ 1010:"Snake",
+ 1011:"FireGoblin",
+ 1012:"Spider",
+ 1013:"EvilMushroom",
+ 1014:"PinkFlower",
+ 1015:"SantaSlime",
+ 1016:"RudolphSlime",
+ 1017:"Bat",
+ 1018:"Pinkie",
+ 1019:"SpikyMushroom",
+ 1020:"Fluffy",
+ 1021:"CaveSnake",
+ 1022:"JackO",
+ 1023:"FireSkull",
+ 1024:"PoisonSkull",
+ 1025:"LogHead",
+ 1026:"MountainSnake",
+ 1027:"EasterFluffy",
+ 1028:"Mouboo",
+ 1029:"MauvePlant",
+ 1030:"CobaltPlant",
+ 1031:"GambogePlant",
+ 1032:"AlizarinPlant",
+ 1033:"SeaSlime",
+ 1034:"GrassSnake",
+ 1035:"Silkworm",
+ 1036:"Zombie",
+ 1037:"CloverPatch",
+ 1038:"Squirrel",
+ 1040:"Wisp",
+ 1041:"Snail",
+ 1042:"Spectre",
+ 1043:"Skeleton",
+ 1044:"LadySkeleton",
+ 1045:"Fallen",
+ 1046:"SnakeLord",
+ 1047:"Poltergeist",
+ 1049:"Bee",
+ 1055:"Butterfly",
+ 1056:"CaveMaggot",
+ 1057:"AngryScorpion",
+ 1058:"IceGoblin",
+ 1059:"GCMaggot",
+ 1060:"Archant",
+ 1061:"Moggun",
+ 1062:"Terranite",
+ 1063:"Pumpkin",
+ 1064:"Bandit",
+ 1065:"BanditLord",
+ 1066:"VampireBat",
+ 1067:"Reaper",
+ 1068:"Reaper2",
+ 1069:"Scythe",
+ 1070:"BallLightning",
+ 1071:"IceElement",
+ 1072:"Yeti",
+ 1073:"TheLost",
+ 1077:"DrunkenSkeleton",
+ 1078:"TipsySkeleton",
+ 1079:"DrunkenLadySkeleton",
+ 1080:"BlueSpark",
+ 1081:"RedSpark",
+ 1082:"Serqet",
+ 1083:"HuntsmanSpider",
+ 1084:"CrotcherScorpion",
+ 1085:"IceSkull",
+ 1086:"FeyElement",
+ 1087:"Larvern",
+ 1088:"Hyvern",
+ 1089:"HungryFluffy",
+ 1090:"Wolvern",
+ 1091:"BlueSlime",
+ 1092:"SlimeBlast",
+ 1093:"WhiteSlime",
+ 1094:"Reinboo",
+ 1095:"WhiteBell",
+ 1096:"SoulSnake",
+ 1097:"SoulEater",
+ 1098:"CopperSlime",
+ 1099:"SleepingBandit",
+ 1100:"AzulSlime",
+ 1101:"DemonicSpirit",
+ 1102:"Luvia",
+ 1103:"WitchGuard",
+ };
+ mob.nameByServerID = function(serverID) {
+ return serverID in mobs ? mobs[serverID] : "UNDEFINED";
+ }
+ return mob;
+}();
diff --git a/public/js/comp/stat.js b/public/js/comp/stat.js
new file mode 100644
index 0000000..cd75646
--- /dev/null
+++ b/public/js/comp/stat.js
@@ -0,0 +1,153 @@
+/**
+ * Make computations about tmwAthena's stat requirements.
+ *
+ * Amongst other things, this can be used to derive a minimum base level for a stat configuration.
+ */
+var stat = function(){
+ var stat = {};
+ var statpoint = [
+ 48,
+ 52,
+ 56,
+ 60,
+ 64,
+ 69,
+ 74,
+ 79,
+ 84,
+ 90,
+ 96,
+ 102,
+ 108,
+ 115,
+ 122,
+ 129,
+ 136,
+ 144,
+ 152,
+ 160,
+ 168,
+ 177,
+ 186,
+ 195,
+ 204,
+ 214,
+ 224,
+ 234,
+ 244,
+ 255,
+ 266,
+ 277,
+ 288,
+ 300,
+ 312,
+ 324,
+ 336,
+ 349,
+ 362,
+ 375,
+ 388,
+ 402,
+ 416,
+ 430,
+ 444,
+ 459,
+ 474,
+ 489,
+ 504,
+ 520,
+ 536,
+ 552,
+ 568,
+ 585,
+ 602,
+ 619,
+ 636,
+ 654,
+ 672,
+ 690,
+ 708,
+ 727,
+ 746,
+ 765,
+ 784,
+ 804,
+ 824,
+ 844,
+ 864,
+ 885,
+ 906,
+ 927,
+ 948,
+ 970,
+ 992,
+ 1014,
+ 1036,
+ 1059,
+ 1082,
+ 1105,
+ 1128,
+ 1152,
+ 1176,
+ 1200,
+ 1224,
+ 1249,
+ 1274,
+ 1299,
+ 1324,
+ 1350,
+ 1376,
+ 1402,
+ 1428,
+ 1455,
+ 1482,
+ 1509,
+ 1536,
+ 1564,
+ 1592
+ ];
+ /* If a character is using a certain number of status points, what is the lowest level it could be? */
+ stat.minLevelForStatusPoints = function(statusPoints) {
+ /*
+ * tmwAthena status points for a level are described by the following recurrence relation:
+ * p(1) = 48
+ * p(l) = p(l - 1) + floor((l + 14) / 4)
+ * For whatever reason, the server also loads a cached copy from a file of all places - db/statpoint.txt.
+ * If this is out of sync with the equation, fun things can happen.
+ * For now, naively assume that statpoint.txt is correct and shamelessly dump it here (see above).
+ */
+ return crossfilter.bisect.left(statpoint, statusPoints, 0, statpoint.length) + 1;
+ };
+ var statusPointInc = [0, 0]
+ stat.statusPointsForStat = function(v) {
+ if (isNaN(v)) {
+ throw "Invalid input";
+ }
+ /* First, convert the absolute stat to terms of increments. */
+ /* This is as simple as removing the starting value - 1 - from it. */
+ /*
+ * The status points needed to increase from a stat with value v can be calculated as follows:
+ * floor((v + 9) / 10) + 1
+ */
+ if (statusPointInc.length > v) {
+ return statusPointInc[v];
+ }
+ var i = stat.statusPointsForStat(v - 1) + Math.floor(((v - 1) + 9) / 10) + 1;
+ statusPointInc.push(i);
+ return i;
+ }
+ /* If a character has a certain arrangement of attributes, how many status points are required for this configuration? */
+ stat.statusPointsForStats = function(str, agi, vit, dex, int, luk) {
+ return stat.statusPointsForStat(str) +
+ stat.statusPointsForStat(agi) +
+ stat.statusPointsForStat(vit) +
+ stat.statusPointsForStat(dex) +
+ stat.statusPointsForStat(int) +
+ stat.statusPointsForStat(luk);
+ };
+ /* Helper function currying minLevelForStatusPoints and stat.statusPointsForStats */
+ stat.minLevelForStats = function(str, agi, vit, dex, int, luk) {
+ return stat.minLevelForStatusPoints(stat.statusPointsForStats(str, agi, vit, dex, int, luk));
+ };
+ return stat;
+}();
diff --git a/public/js/crossfilter b/public/js/crossfilter
new file mode 160000
+Subproject ae994e8f8014ad4304e0575973bb23c1fb1ac0b
diff --git a/public/js/d3 b/public/js/d3
new file mode 160000
+Subproject 91d35b4205d4ef150c61c415b7379404bced726
diff --git a/public/js/dc b/public/js/dc
new file mode 160000
+Subproject 649860c7f007ae41a89e1d6236748ba36292288
diff --git a/public/js/mv/chart.js b/public/js/mv/chart.js
new file mode 100644
index 0000000..d06d40e
--- /dev/null
+++ b/public/js/mv/chart.js
@@ -0,0 +1,131 @@
+var mv = function(mv) {
+ mv.charts = {};
+ var thinWidth = 250;
+ var medWidth = 400;
+ var wideWidth = Math.max(700, document.width - thinWidth - medWidth);
+ 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(med(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.wpn = 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!";
+ }})
+ .filter(2)
+ ;
+ 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.sqrt(d.value.r); })
+ .maxBubbleRelativeSize(0.045)
+ .x(d3.scale.log().domain([1, 100000]))
+ .y(d3.scale.log().domain([1, 300000]))
+ .axisPixelPadding({left:5, top: 10, right: 15, bottom: 5})
+ .elasticX(true)
+ .elasticY(true)
+ .renderHorizontalGridLines(true)
+ .renderVerticalGridLines(true)
+ .title(function(d) { return "Map " + d.key + ":" + d.value.r; })
+ .renderTitle(true)
+ ;
+ mv.charts.stats = trellisChart("#stat-chart", ["str", "agi", "vit", "dex", "int", "luk"].map(function(d) { mv.heap[d].name = d; return mv.heap[d]; }));
+ dc.renderlet(function() { mv.charts.stats(); });
+ dc.renderAll();
+ }
+ charter.filters = function() {
+ var r = {}, f;
+ for (var k in mv.charts) {
+ f = mv.charts[k].filter();
+ if (f != null) {
+ r[k] = f;
+ }
+ }
+ return r;
+ }
+ 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(wideWidth)
+ ;
+ }
+ function med(chart) {
+ return chart
+ .width(medWidth)
+ ;
+ }
+ function thin(chart) {
+ return chart
+ .width(thinWidth)
+ ;
+ }
+ 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)
+ .transitionDuration(500)
+ ;
+ }
+ 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(chart)
+ .radius(90)
+ .colorCalculator(d3.scale.category20c())
+ ;
+ }
+ return mv;
+}(mv || {});
diff --git a/public/js/mv/connect.js b/public/js/mv/connect.js
new file mode 100644
index 0000000..aba4c33
--- /dev/null
+++ b/public/js/mv/connect.js
@@ -0,0 +1,156 @@
+var mv = function(mv) {
+ mv.socket = {
+ connect: connect
+ };
+ /* If we're updating due to something we received, then don't broadcast back. */
+ var netrendering = false;
+ /* Our ID */
+ var id = 0;
+ /* List of all users */
+ var users = {};
+ /* The user status box */
+ var usersStatus = d3.select("#users-status");
+ /* io.socket's socket */
+ var socket;
+ function connect() {
+ socket = io.connect('http://localhost:3000');
+ socket.on("connect", function() { console.log("CONNECT", arguments); });
+ socket.on("disconnect", function() { console.log("DISCONNECT", arguments); });
+ socket.emit('login');
+ /*
+ * Protocol:
+ * selflogin -> id (I)
+ * login -> id (I), nick (S)
+ * nickset -> id (I), nick (S)
+ * users -> { id -> {nick (S), filters ({dim (S) -> filter (*)}) } }
+ * logout -> id (I)
+ */
+ socket.on('selflogin', function(d) {
+ /* Acknowledged that we logged in */
+ /* Take note of our ID. */
+ id = d.id;
+ });
+ socket.on('login', function(d) {
+ /* Someone else logging in */
+ users[d.id] = { nick: d.nick, filters: {} };
+ updateUsersStatus();
+ });
+ socket.on('users', function(d) {
+ /* We've got a list of all users. */
+ /* The server is always right. */
+ users = d.users;
+ updateUsersStatus();
+ });
+ socket.on('nickset', function(d) {
+ /* Someone, possibly us, changed their nick. */
+ users[d.id].nick = d.nick;
+ updateUsersStatus();
+ });
+ socket.on('filterset', function(d) {
+ /* Someone changed their filter */
+ users[d.id].filters = d.filters;
+ /* Use the variable netrendering to denote that we're rendering due to a change received from the network. */
+ netrendering = true;
+ setOwnFilters(d.filters);
+ netrendering = false;
+ });
+ socket.on('logout', function(d) {
+ /* Someone disconnected, take them off the list. */
+ delete users[d.id];
+ updateUsersStatus();
+ });
+ dc.renderlet(function() {
+ /* Hook a listener into dc's rendering routine. If it rerenders, broadcast the change. */
+ if (netrendering) {
+ /* If we rendered due a change we received, don't broadcast it again. That would be A Bad Thing. */
+ return;
+ }
+ socket.emit("filter", { filters: mv.charter.filters() });
+ });
+ }
+ function setOwnFilters(filters) {
+ /* See if there's any difference - if there isn't, don't update. */
+ var change = false;
+ var key;
+ /* Check for keys in the filters to apply which are not in our charts. */
+ for (key in filters) {
+ if (!(key in mv.charts))
+ continue;
+ var filter = mv.charts[key].filter();
+ if (typeof(filter) == "array") {
+ /* Crossfilter uses arrays to filter ranges. Exactly the first two elements are significant. */
+ if (filter[0] == filters[key][0] &&
+ filter[1] == filters[key][1]) {
+ continue;
+ }
+ } else if (filter == filters[key]) {
+ continue;
+ }
+ /* This filter differs. Apply it. */
+ change = true;
+ mv.charts[key].filter(filters[key]);
+ }
+ /* Check for keys in our charts which are not in the filters to apply. */
+ for (key in mv.charts) {
+ if (mv.charts[key].filter() != null) {
+ if (key in filters) {
+ /* This has already been handled above */
+ continue;
+ }
+ /* There is no longer a filter applying here, clear it. */
+ change = true;
+ mv.charts[key].filterAll();
+ }
+ }
+ if (change) {
+ dc.redrawAll();
+ }
+ }
+ function updateUsersStatus() {
+ /* Convert the user list to a form suitable for a d3 selection */
+ var data = [];
+ for (var uid in users) { users[uid].id = uid; data.push(users[uid]); }
+ /* Data join */
+ var userlist = usersStatus.selectAll(".user")
+ .data(data, function(d) { return d.id; });
+ /* Enter */
+ userlist
+ .enter().append("li").attr("class", "user")
+ ;
+ /* Update */
+ userlist
+ .each(function(d,i) {
+ var elm = d3.select(this);
+ console.log("Userlist para appending", d,i);
+ var name = elm.select(".name");
+ var nick = d.nick == null ? "Anonymous User " + d.id : d.nick;
+ if (d.id == id) {
+ /* This is us! We can edit our name. */
+ if (name.empty()) {
+ console.log("Found our entry. id:", id, "datum", d);
+ name = elm.append("input").attr("class", "name")
+ .attr("type", "text")
+ .attr("placeholder", "Enter name here")
+ .on("change", function () {
+ /* A d3 select must be dereferenced twice to access the DOM entity */
+ socket.emit("nick", { nick: name[0][0].value });
+ })
+ ;
+ }
+ name.attr("value", nick);
+ } else {
+ /* This is someone else. We can't edit their name. */
+ if (name.empty()) {
+ name = elm.append("p").attr("class", "name");
+ }
+ name.text(nick);
+ }
+ })
+ ;
+ /* Remove */
+ userlist
+ .exit().remove()
+ ;
+ }
+ return mv;
+}(mv || {});
diff --git a/public/js/mv/heap.js b/public/js/mv/heap.js
new file mode 100644
index 0000000..03f3c00
--- /dev/null
+++ b/public/js/mv/heap.js
@@ -0,0 +1,63 @@
+var mv = function(mv) {
+ mv.heap = function() {
+ var heap = {};
+ var monoGroups = {};
+ var statGran = 10;
+ heap.init = function() {
+ function ea(p, d) { p.e += d.e; p.j += d.j; p.r++; return p; }
+ function es(p, d) { p.e -= d.e; p.j -= d.j; p.r--; return p; }
+ function ez(p, d) { return { e: 0, j: 0, r: 0 }; }
+ heap.cfdata = crossfilter(mv.parser.records);
+ heap.all = heap.cfdata.groupAll().reduce(ea, es, ez);
+ 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(ea, es, ez);
+ 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; });
+ function sa(p, d) {
+ if (!d.pcstat) return p;
+ p.str[d.pcstat.str]++ || (p.str[d.pcstat.str] = 1);
+ p.agi[d.pcstat.agi]++ || (p.agi[d.pcstat.agi] = 1);
+ p.vit[d.pcstat.vit]++ || (p.vit[d.pcstat.vit] = 1);
+ p.dex[d.pcstat.dex]++ || (p.dex[d.pcstat.dex] = 1);
+ p.int[d.pcstat.int]++ || (p.int[d.pcstat.int] = 1);
+ p.luk[d.pcstat.luk]++ || (p.luk[d.pcstat.luk] = 1);
+ return p;
+ }
+ function ss(p, d) {
+ if (!d.pcstat) return p;
+ --p.str[d.pcstat.str] || (p.str[d.pcstat.str] = undefined);
+ --p.agi[d.pcstat.agi] || (p.agi[d.pcstat.agi] = undefined);
+ --p.vit[d.pcstat.vit] || (p.vit[d.pcstat.vit] = undefined);
+ --p.dex[d.pcstat.dex] || (p.dex[d.pcstat.dex] = undefined);
+ --p.int[d.pcstat.int] || (p.int[d.pcstat.int] = undefined);
+ --p.luk[d.pcstat.luk] || (p.luk[d.pcstat.luk] = undefined);
+ return p;
+ }
+ function sz(p, d) { return { str: [], agi: [], vit: [], dex: [], int: [], luk: [] }; }
+ monoGroup("str", function(d) { return d.pcstat ? d.pcstat.str : 0; }).reduce(sa, ss, sz);
+ monoGroup("agi", function(d) { return d.pcstat ? d.pcstat.agi : 0; }).reduce(sa, ss, sz);
+ monoGroup("vit", function(d) { return d.pcstat ? d.pcstat.vit : 0; }).reduce(sa, ss, sz);
+ monoGroup("dex", function(d) { return d.pcstat ? d.pcstat.dex : 0; }).reduce(sa, ss, sz);
+ monoGroup("int", function(d) { return d.pcstat ? d.pcstat.int : 0; }).reduce(sa, ss, sz);
+ monoGroup("luk", function(d) { return d.pcstat ? d.pcstat.luk : 0; }).reduce(sa, ss, sz);
+ /* 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/public/js/mv/load.js b/public/js/mv/load.js
new file mode 100644
index 0000000..39dd391
--- /dev/null
+++ b/public/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() {
+ reader.readAsBinaryString(fevt.target.files[curfile]);
+ }
+ nextFile();
+ }, false);
+ };
+ return loader;
+ }();
+ return mv;
+}(mv || {});
diff --git a/public/js/mv/main.js b/public/js/mv/main.js
new file mode 100644
index 0000000..b7fcb1e
--- /dev/null
+++ b/public/js/mv/main.js
@@ -0,0 +1,59 @@
+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();
+ }
+ 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();
+ console.log(document.getElementById("connect-option").checked);
+ if (document.getElementById("connect-option").checked) {
+ mv.socket.connect();
+ }
+ }, 2000);
+ }
+ };
+ return mv;
+}(mv || {});
diff --git a/public/js/mv/parse.js b/public/js/mv/parse.js
new file mode 100644
index 0000000..8094bc5
--- /dev/null
+++ b/public/js/mv/parse.js
@@ -0,0 +1,109 @@
+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: "UNKNOWN",
+ dmg: -1010,
+ wpn: "UNKNOWN",
+ atktype: "UNKNOWN"
+ };
+ 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 = mob.nameByServerID(d[7]);
+ softAssert(rec.target, "Unknown target!")
+ rec.dmg = parseInt(d[8]);
+ rec.wpn = item.nameByServerID(d[9]);
+ rec.atktype = "Physical";
+ } else {
+ /* Not weapon damage, perhaps it was spell damage? */
+ d = spl[i - 2].match(/^(\d+\.\d+) PC(\d+) (\d+):(\d+),(\d+) SPELLDMG MOB(\d+) (\d+) FOR (\d+) BY ([^ ]+)/);
+ if (d) {
+ rec.target = mob.nameByServerID(d[7]);
+ rec.dmg = parseInt(d[8]);
+ rec.wpn = d[9];
+ rec.atktype = "Magical";
+ }
+// console.error("No match (deathblow):", spl[i - 2]);
+ }
+ } 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;
+ softAssert(rec.target, "Unknown (cloned) target!");
+ rec.dmg = clone.dmg; /* FIXME: Take into account actual assist damage */
+ rec.wpn = clone.wpn;
+ rec.atktype = clone.atktype; /* FIXME: Take into account what the assists used */
+ } else {
+// console.error("No match (clone):", spl[i - 1]);
+ }
+ }
+ }
+ 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);
+ s.str = Math.floor(s.str / 10);
+ s.agi = Math.floor(s.agi / 10);
+ s.vit = Math.floor(s.vit / 10);
+ s.int = Math.floor(s.int / 10);
+ s.dex = Math.floor(s.dex / 10);
+ s.luk = Math.floor(s.luk / 10);
+ pcstat[d[1]] = s;
+ return;
+ }
+ });
+ };
+ function softAssert(expr, msg) {
+ if (!expr) {
+ console.error("SOFTASSERT FAILURE: " + msg);
+ }
+ }
+ return parser;
+ }();
+ return mv;
+}(mv || {});
diff --git a/public/js/util/memoize.js b/public/js/util/memoize.js
new file mode 100644
index 0000000..796c060
--- /dev/null
+++ b/public/js/util/memoize.js
@@ -0,0 +1,45 @@
+/*
+ * Helper function for memoization
+ *
+ * The passed labeller function must generate unique and exact names for entries that are naturally equal.
+ * IDs are persistent.
+ *
+ * names, idByName, and entries are accessible fields from the memoizer.
+ * Behaviour on external modification of these fields is undefined.
+ *
+ * An entry object passed to the memoizer has its field "id" set to the memoized id by default.
+ * The name of the ID field can be modified by calling the getter/setter idField() from the memoizer.
+ * Behaviour on changing the ID field used while the memoizer has already performed memoization is undefined.
+ */
+function memoize(labeller) {
+ var idField = "id";
+ var names = [];
+ var idByName = {};
+ var entries = [];
+ var _memoizer = function(entry) {
+ var name = labeller(entry);
+ if (name in idByName) {
+ /* We already have a suitable entry. */
+ var id = idByName[name];
+ return entries[id];
+ }
+ /* New entry. */
+ /* Set the entry's ID. */
+ entry[idField] = entries.length;
+ /* Index the new entry. */
+ idByName[name] = entry[idField];
+ names[entry[idField]] = name;
+ entries.push(entry);
+
+ return entry;
+ }
+ _memoizer.names = function() { return names; };
+ _memoizer.idByName = function() { return idByName; };
+ _memoizer.entries = function() { return entries; };
+ _memoizer.idField = function(x) {
+ if (!arguments.length) return idField;
+ idField = x;
+ return _memoizer;
+ };
+ return _memoizer;
+}
diff --git a/public/js/util/progress.js b/public/js/util/progress.js
new file mode 100644
index 0000000..060e6dc
--- /dev/null
+++ b/public/js/util/progress.js
@@ -0,0 +1,37 @@
+function progress(root) {
+ var _progress = {};
+ var container = document.getElementById(root);
+ var _percent = '0%';
+ var bar = document.querySelector('#' + root + ' .percent');
+ _progress.label = function() {
+ return _percent;
+ }
+ /* Updates the progress bar to display a specific percentage. No range checking performed. */
+ _progress.setPercent = function(percent) {
+ _percent = percent;
+ bar.style.width = _percent;
+ bar.textContent = _progress.label();
+ }
+ /* Updates the progress bar to display a percentage based on the current proportion of items done. */
+ _progress.update = function(current, total) {
+ var percentLoaded = Math.min(100, Math.round((current / total) * 100));
+ _progress.setPercent(percentLoaded + '%');
+ };
+ /* Resets the progress bar to display nothing done. */
+ _progress.reset = function() {
+ _progress.setPercent('0%');
+ }
+ /* Resets the progress bar to display everything done. */
+ _progress.complete = function() {
+ _progress.setPercent('100%');
+ }
+ /* Shows the progress bar. */
+ _progress.show = function() {
+ container.className += ' loading';
+ }
+ /* Hides the progress bar */
+ _progress.hide = function() {
+ container.className = container.className.replace(/\bloading\b/, '');
+ }
+ return _progress;
+}
diff --git a/public/js/util/trellis-chart.js b/public/js/util/trellis-chart.js
new file mode 100644
index 0000000..65e07a2
--- /dev/null
+++ b/public/js/util/trellis-chart.js
@@ -0,0 +1,140 @@
+function trellisChart(anchor, monoGroups) {
+ /* attr -> {dim, group} key -> str amount, value -> { str, agi, vit, dex, int, luk } */
+
+ var attrs = monoGroups.map(function(d) { return d.name; });
+ var attrsIdByName = {};
+ monoGroups.forEach(function(d, i) { attrsIdByName[d.name] = i; });
+
+ var cellWidth = 5;
+ var radius = cellWidth / 2;
+ var subChartLength = 57;
+ var subChartUnpaddedLength = 50;
+ var subChartPadding = 7;
+ var filler = d3.scale.log().domain([1, 2]).range([0, 255]);
+
+ var margin = {top: 10, right: 10, bottom: 20, left: 10};
+ var anchor = d3.select(anchor);
+ var g = anchor.select("g");
+
+ var _chart = function() {
+ if (g.empty()) {
+ /* Make stuff! */
+ var svg = anchor.append("svg");
+ g = svg
+ .append("g");
+ attrs.forEach(function(d, i) {
+ g
+ .append("text")
+ .attr("transform", function(d) { return "translate(0," + ((attrs.length - i) * subChartLength + 10 - subChartLength / 2) + ")"; })
+ .text(d)
+ ;
+ g
+ .append("text")
+ .attr("transform", function(d) { return "translate(" + (i * subChartLength + 25 + 22) + "," + (attrs.length * subChartLength + 18) + ")"; })
+ .text(d)
+ ;
+ })
+ g = svg
+ .append("g")
+ .attr("transform", "translate(" + (margin.left + 25) + "," + (margin.top) + ")");
+ }
+ /* Group first into columns for each stat. We have one column for each of the stat monoGroups. */
+ /*
+ * monoGroups is an array of each stat dimension. We can consider each column to have data in the following format:
+ * { group: function, dim: function, name: stat }
+ */
+ var columns = g.selectAll(".column")
+ .data(monoGroups);
+ var colE = columns
+ .enter().append("g")
+ .attr("class", "column")
+ .attr("transform", function(d) { return "translate(" + (attrsIdByName[d.name] * subChartLength) + ",0)"; })
+ ;
+ colE
+ .append("line")
+ .attr("x1", -cellWidth)
+ .attr("x2", -cellWidth)
+ .attr("y1", -cellWidth)
+ .attr("y2", subChartLength * attrs.length - subChartPadding)
+ .attr("class", "border-line")
+ ;
+ colE
+ .append("line")
+ .attr("x1", subChartUnpaddedLength)
+ .attr("x2", subChartUnpaddedLength)
+ .attr("y1", -cellWidth)
+ .attr("y2", subChartLength * attrs.length - subChartPadding)
+ .attr("class", "border-line")
+ ;
+ /* Each stat has an array for its value. Group these to find the x position. */
+ /*
+ * The function transforms the data to take the grouping. We can consider each x position grouping to have data in the following format:
+ * { key: position, value: [{[stat] -> [y pos] -> count}] }
+ */
+ var colposg = columns.selectAll(".colpos")
+ .data(function(d, i) {
+// console.log("Incoming colposg format:", d, i, "Transformed to:", d.group.all().map(function(d2) { d2.name = d.name; return d2; }));
+ return d.group.all().map(function(d2) { d2.name = d.name; return d2; });
+ }, function(d) { return d.key; });
+ colposg
+ .enter().append("g")
+ .attr("class", "colpos")
+ .attr("transform", function(d) { return "translate(" + (d.key * cellWidth) + ",0)"; })
+ ;
+ /* Next, split up each x position grouping into its y stat grouping. */
+ /*
+ * We can consider each y stat grouping to have data in the following format:
+ * v[y pos] -> count; v.name -> name
+ */
+ var rows = colposg.selectAll(".row")
+ .data(function(d, i) {
+// console.log("Incoming row format:", d, i, "Transformed to:", attrs.map(function(d2) { return { name: d2, data: d.value[d2] }; }));
+ return attrs.map(function(d2) { return { name: d2, data: d.value[d2] }; });
+ });
+ rows
+ .enter().append("g")
+ .attr("class", "row")
+ .attr("transform", function(d) { return "translate(0," + ((attrs.length - attrsIdByName[d.name] - 1) * subChartLength) + ")"; })
+ ;
+ /* Finally, split up each y stat grouping into x. */
+ var vmax = 0;
+ var cells = rows.selectAll(".cell")
+ .data(function(d, i) {
+// console.log("Incoming cells format:", d, i, "Transformed to:", d.data);
+ return d.data;
+ });
+ cells
+ .enter().append("circle")
+ .attr("class", "cell")
+ .attr("r", radius)
+ ;
+ cells
+ .each(function(d) {
+ if (d > vmax) vmax = d;
+ })
+ ;
+ filler.domain([1, vmax + 1]);
+ cells
+ .attr("fill", function(d) {
+ return d ? d3.rgb(255 - filler(d + 1), 255 - filler(d + 1) / 2, 255 - filler(d + 1) / 3) : "white";
+ })
+ .attr("transform", function(d, i) { return "translate(0," + ((10-i) * cellWidth) + ")"; })
+ ;
+ cells
+ .exit()
+ .remove()
+ ;
+ }
+
+ _chart.filter = function() {
+ /*
+ * TODO:
+ * This is going to be interesting. As the chart is not charting a single
+ * monogroup, and most code is built around this assumption, this might
+ * well end up being a messy special case.
+ */
+ return null;
+ }
+
+ return _chart;
+}