1HERO_CACHE = "https://e7-optimizer-game-data.s3-accelerate.amazonaws.com/herodata.json?"; 2heroData = {}; 3heroesById = {}; 4$ = jQuery 5dev = false; 6 7loadedHeroData = false; 8loadedGwdb = false; 9 10selector0 = null 11selector1 = null 12selector2 = null 13selector3 = null 14selector4 = null 15 16buildDefSelector0 = null 17buildDefSelector1 = null 18buildDefSelector2 = null 19buildDefSelector3 = null 20buildDefSelector4 = null 21 22jQuery(document).ready(function($){ 23 document.title = "Fribbels GW Meta Tracker" 24 $("#homeLink").attr("href", window.location.href.split('?')[0]) 25 26 var options = { 27 sortField: 'text', 28 width: 'resolve', // need to override the changed default 29 placeholder: "Select hero", 30 templateResult: formatHeroList, 31 theme: "classic", 32 allowClear: true 33 } 34 var includeOptions = { 35 sortField: 'text', 36 width: 'resolve', // need to override the changed default 37 placeholder: "Include hero", 38 templateResult: formatHeroList, 39 theme: "classic", 40 allowClear: true, 41 } 42 var excludeOptions = { 43 sortField: 'text', 44 width: 'resolve', // need to override the changed default 45 placeholder: "Exclude hero", 46 templateResult: formatHeroList, 47 theme: "classic", 48 allowClear: true 49 } 50 51 selector0 = $('#heroSelector0').select2(options); 52 selector1 = $('#heroSelector1').select2(options); 53 selector2 = $('#heroSelector2').select2(options); 54 selector3 = $('#heroSelector3').select2(includeOptions); 55 selector4 = $('#heroSelector4').select2(excludeOptions); 56 57 buildDefSelector0 = $('#buildDefHeroSelector0').select2(options); 58 buildDefSelector1 = $('#buildDefHeroSelector1').select2(options); 59 buildDefSelector2 = $('#buildDefHeroSelector2').select2(options); 60 buildDefSelector3 = $('#buildDefHeroSelector3').select2(includeOptions); 61 buildDefSelector4 = $('#buildDefHeroSelector4').select2(excludeOptions); 62 63 $("#searchButton").click(search) 64 $("#buildDefSearchButton").click(buildDefSearch) 65 66 var queryString = window.location.search; 67 var urlParams = new URLSearchParams(queryString).get('def'); 68 69 if (urlParams) { 70 $('#resultRows').html("Loading..") 71 } else { 72 $('#metaRows').html("Loading...") 73 } 74 75 76 fetchCache(HERO_CACHE).then(x => { 77 console.log("herodata", x) 78 heroData = x; 79 80 for (var name of Object.keys(heroData)) { 81 heroesById[heroData[name].code] = name; 82 } 83 84 for (var value of Object.values(heroData)) { 85 var img=new Image(); 86 img.src=value.assets.icon; 87 } 88 89 90 var entries = Object.entries(heroesById).sort(function compare(a, b) { 91 if (a[1] < b[1]) 92 return -1; 93 if (a[1] > b[1]) 94 return 1; 95 return 0; 96 }) 97 98 for (var entry of entries) { 99 var data = { 100 id: entry[0], 101 text: entry[1] 102 }; 103 104 var newOption0 = new Option(data.text, data.id, false, false); 105 var newOption1 = new Option(data.text, data.id, false, false); 106 var newOption2 = new Option(data.text, data.id, false, false); 107 var newOption3 = new Option(data.text, data.id, false, false); 108 var newOption4 = new Option(data.text, data.id, false, false); 109 $('#heroSelector0').append(newOption0); 110 $('#heroSelector1').append(newOption1); 111 $('#heroSelector2').append(newOption2); 112 $('#heroSelector3').append(newOption3); 113 $('#heroSelector4').append(newOption4); 114 115 var buildDefNewOption0 = new Option(data.text, data.id, false, false); 116 var buildDefNewOption1 = new Option(data.text, data.id, false, false); 117 var buildDefNewOption2 = new Option(data.text, data.id, false, false); 118 var buildDefNewOption3 = new Option(data.text, data.id, false, false); 119 var buildDefNewOption4 = new Option(data.text, data.id, false, false); 120 $('#buildDefHeroSelector0').append(buildDefNewOption0); 121 $('#buildDefHeroSelector1').append(buildDefNewOption1); 122 $('#buildDefHeroSelector2').append(buildDefNewOption2); 123 $('#buildDefHeroSelector3').append(buildDefNewOption3); 124 $('#buildDefHeroSelector4').append(buildDefNewOption4); 125 } 126 127 try { 128 if (urlParams) {
129 var names = urlParams.split(",") 130 var ids = names.map(x => Object.entries(heroesById).find(y => y[1] == x)[0]) 131 132 selector0.val(ids[0]).trigger("change"); 133 selector1.val(ids[1]).trigger("change"); 134 selector2.val(ids[2]).trigger("change"); 135 136 search(); 137 } 138 } catch (e) { 139 console.error("Url parsing failed", e); 140 } 141 142 loadedHeroData = true; 143 checkReady(); 144 }).catch((e) => { 145 console.log(e) 146 $('#metaRows').html("</br></br></br>No data for recent guild wars") 147 }) 148 149}); 150 151function checkReady() { 152 if (!loadedHeroData) { 153 return; 154 } 155 156 showMeta() 157} 158 159function showMeta() { 160 var queryString = window.location.search; 161 var urlParams = new URLSearchParams(queryString).get('def'); 162 163 $.ajax({ 164 url: dev ? "http://127.0.0.1:5000/getMeta" : "https://z4tfy2r5kc.execute-api.us-west-2.amazonaws.com/dev/getMeta", 165 //force to handle it as text 166 dataType: "text", 167 type: "POST", 168 crossDomain: true, 169 data: "none", 170 success: function(data) { 171 var json = $.parseJSON(data); 172 console.log("meta", json) 173 var defenses = json.data 174 var offenses = Object.entries(json.offenseData) 175 var totalSize = json.totalSize 176 177 $('#intro').html(`This app tracks data from ${totalSize.toLocaleString("en-US")} attacks from top 50 ranked guild wars. Latest update: ${new Date(json.maxTimestamp*1000).toDateString()}.`) 178 179 if (urlParams) { 180 return; 181 } 182 183 defenses.sort((a, b) => (b.w+b.l) - (a.w+a.l)) 184 offenses.sort((a, b) => (b[1].w+b[1].l) - (a[1].w+a[1].l)) 185 186 var html = "</br></br><h2>Top 50 most common meta defenses in past 4 weeks</h2>"; 187 for (var i = 0; i < 50; i++) { 188 var defense = defenses[i]; 189 var percent = (defense.w/(defense.l + defense.w) * 100).toFixed(1); 190 191 html += 192` 193<div class="resultRow"> 194 <div class="imageRow"> 195 <a href="${"gw-meta.html?def=" + defense.defense.split(",").map(x => heroesById[x]).join(",")}"> 196 <div class="metaFightIcons"> 197 ${imgHtml(defense.defense)} 198 <div class="vSpace"></div> 199 </div> 200 </a> 201 <div class="resultsContainer"> 202 <div class="metaResults W"> 203 ${defense.w} 204 </div> 205 <img class="metaAtkImg" src="battle_pvp_icon_def.png"></img> 206 207 <div class="metaResults L"> 208 ${defense.l} 209 </div> 210 <img class="metaAtkImg" src="battle_pvp_icon_defeat.png"></img> 211 212 <div class="metaResultsPercent"> 213 ${isNaN(percent) ? "No results" : percent + " %"} 214 </div> 215 </div> 216 </div> 217</div> 218` 219 } 220 221 html += "</br></br><h2>Top 50 most common meta offense units in past 4 weeks</h2>" 222 223 for (var i = 0; i < 50; i++) { 224 var offenseName = offenses[i][0]; 225 var offenseWL = offenses[i][1]; 226 var percent = (offenseWL.w/(offenseWL.l + offenseWL.w) * 100).toFixed(1); 227 // console.log(percent) 228 // console.log(offenseWL) 229 html += 230` 231<div class="resultRow"> 232 <div class="imageRow"> 233 <div class="metaFightIconsOffense"> 234 ${imgHtml(offenseName)} 235 <div class="vSpace"></div> 236 </div> 237 </a> 238 <div class="resultsContainer"> 239 <div class="metaResults W"> 240 ${offenseWL.w} 241 </div> 242 <img class="metaAtkImg" src="battle_pvp_icon_win.png"></img> 243 244 <div class="metaResults L"> 245 ${offenseWL.l} 246 </div> 247 <img class="metaAtkImg" src="battle_pvp_icon_lose.png"></img> 248 249 <div class="metaResultsPercent"> 250 ${isNaN(percent) ? "No results" : percent + " %"} 251 </div> 252 </div> 253 </div> 254</div> 255` 256 257 } 258 259 $('#metaRows').html(html) 260 } 261 }) 262} 263 264function formatHeroList(hero) { 265 if (!hero.id) { 266 return hero.text 267 } 268 var output = $(`<div class="searchRowContainer"><img src="${heroData[hero.text].assets.icon}" class="heroSearchIcon" />${hero.text}</div>`); 269 270 return output; 271}; 272 273function search() { 274 heroes = [ 275 $('#heroSelector0').select2('data')[0], 276 $('#heroSelector1').select2('data')[0], 277 $('#heroSelector2').select2('data')[0] 278 ] 279 var defenseKey = heroes.map(x => x.id).sort() 280 // console.log("defkey", defenseKey); 281 282 $('#resultRows').html("Loading..") 283 var defenseHtml = imgHtml(defenseKey.join(",")) 284 $('#defenseIcons').html("<br/>" + defenseHtml) 285 286 287 var names = defenseKey.map(x => heroesById[x]).join(",") 288 window.history.replaceState(null, null, "?def=" + names); 289 290 $.ajax({ 291 url: dev ? "http://127.0.0.1:5000/getDef" : "https://z4tfy2r5kc.execute-api.us-west-2.amazonaws.com/dev/getDef", 292 //force to handle it as text 293 dataType: "text", 294 type: "POST", 295 crossDomain: true, 296 data: defenseKey.join(","), 297 success: function(data) { 298 //data downloaded so we call parseJSON function 299 //and pass downloaded data 300 var json = $.parseJSON(data); 301 //now json variable contains data in json format 302 //let's display a few items 303 console.log("getDefResponse", json); 304 305 offenseComps = json.data; 306 307 if (!offenseComps) { 308 $('#resultRows').html("No results") 309 return 310 } 311 312 313 include = $('#heroSelector3').select2('data')[0].id 314 exclude = $('#heroSelector4').select2('data')[0].id 315 // console.log("filter", include) 316 // console.log("exclude", exclude) 317 // var offenses = {} 318 // for (var fight of fights) { 319 // if (!offenses[fight.offense]) { 320 // offenses[fight.offense] = [] 321 // } 322 // offenses[fight.offense].push(fight) 323 // } 324 325 // offenses = Object.keys(offenses).map(x => ({ 326 // offense: x, 327 // fights: offenses[x] 328 // })) 329 330 offenses = Object.entries(offenseComps).sort(function compare(a, b) { 331 if (a[1].w + a[1].l < b[1].w + b[1].l) 332 return 1; 333 if (a[1].w + a[1].l > b[1].w + b[1].l) 334 return -1; 335 return 0; 336 }).filter(x => { 337 if (include.length == 0) 338 return true 339 else 340 return x[0].includes(include); 341 }).filter(x => { 342 if (exclude.length == 0) 343 return true 344 else 345 return !x[0].includes(exclude); 346 }) 347 348 $('#resultRows').html("") 349 350 // console.log("offenses", offenses) 351 352 var html = "" 353 354 for (var i = 0; i < Math.min(100, offenses.length); i++) { 355 // for (var offense of offenses) { 356 var offense = offenses[i] 357 358 var percent = (offense[1].w/(offense[1].l + offense[1].w) * 100).toFixed(1); 359 360 html += 361// ` 362// <div class="resultRow"> 363// <div class="imageRow"> 364// <div class="fightIcons"> 365// ${imgHtml(offense[0])} 366// </div> 367// <div class="resultsContainer"> 368// <div class="results W">${offense[1].w}W</div> 369// <div class="results L">${offense[1].l}L</div> 370// </div> 371// <div class="metaResultsPercent"> 372// ${isNaN(percent) ? "No results" : percent + " %"} 373// </div> 374// </div> 375// </div> 376// ` 377 ` 378<div class="resultRow"> 379 <div class="imageRow"> 380 <div class="metaFightLookup"> 381 ${imgHtml(offense[0])} 382 </div> 383 <div class="resultsContainer"> 384 <div class="metaResults W"> 385 ${offense[1].w} 386 </div> 387 <img class="metaAtkImg" src="battle_pvp_icon_win.png"></img> 388 389 <div class="metaResults L"> 390 ${offense[1].l} 391 </div> 392 <img class="metaAtkImg" src="battle_pvp_icon_lose.png"></img> 393 394 <div class="metaResultsPercent"> 395 ${isNaN(percent) ? "No results" : percent + " %"} 396 </div> 397 </div> 398 </div> 399</div> 400` 401 } 402 403 $('#resultRows').html(html) 404 $('#metaRows').html("") 405 } 406 }); 407} 408 409function buildDefSearch() { 410 heroes = [ 411 $('#buildDefHeroSelector0').select2('data')[0], 412 $('#buildDefHeroSelector1').select2('data')[0], 413 $('#buildDefHeroSelector2').select2('data')[0] 414 ] 415 416 var defenseKey = heroes.map(x => x.id).sort() 417 // console.log("defkey", defenseKey); 418 419 $('#buildDefResultRows').html("Loading..") 420 var defenseHtml = imgHtml(defenseKey.join(",")) 421 $('#buildDefDefenseIcons').html("<br/>" + defenseHtml) 422 423 424 var names = defenseKey.map(x => heroesById[x]).join(",") 425 426 427 $.ajax({ 428 url: dev ? "http://127.0.0.1:5000/buildDef" : "https://z4tfy2r5kc.execute-api.us-west-2.amazonaws.com/dev/buildDef", 429 //force to handle it as text 430 dataType: "text", 431 type: "POST", 432 crossDomain: true, 433 data: defenseKey.join(","), 434 success: function(data) { 435 //data downloaded so we call parseJSON function 436 //and pass downloaded data 437 var json = $.parseJSON(data); 438 //now json variable contains data in json format 439 //let's display a few items 440 console.log("getDefResponse", json); 441 442 offenseComps = json.data; 443 444 if (!offenseComps) { 445 $('#buildDefResultRows').html("No results") 446 return 447 } 448 449 450 include = $('#buildDefHeroSelector3').select2('data')[0].id 451 exclude = $('#buildDefHeroSelector4').select2('data')[0].id 452 // console.log("filter", include) 453 // console.log("exclude", exclude) 454 // var offenses = {} 455 // for (var fight of fights) { 456 // if (!offenses[fight.offense]) { 457 // offenses[fight.offense] = [] 458 // } 459 // offenses[fight.offense].push(fight) 460 // } 461 462 // offenses = Object.keys(offenses).map(x => ({ 463 // offense: x, 464 // fights: offenses[x] 465 // })) 466 467 offenses = Object.entries(offenseComps).sort(function compare(a, b) { 468 if (a[1].w + a[1].l < b[1].w + b[1].l) 469 return 1; 470 if (a[1].w + a[1].l > b[1].w + b[1].l) 471 return -1; 472 return 0; 473 }).filter(x => { 474 if (include.length == 0) 475 return true 476 else 477 return x[0].includes(include); 478 }).filter(x => { 479 if (exclude.length == 0) 480 return true 481 else 482 return !x[0].includes(exclude); 483 }) 484 485 $('#buildDefResultRows').html("") 486 487 // console.log("offenses", offenses) 488 489 var html = "" 490 491 for (var i = 0; i < Math.min(200, offenses.length); i++) { 492 // for (var offense of offenses) { 493 var offense = offenses[i] 494 495 var percent = (offense[1].l/(offense[1].l + offense[1].w) * 100).toFixed(1); 496 497 html += 498// ` 499// <div class="resultRow"> 500// <div class="imageRow"> 501// <div class="fightIcons"> 502// ${imgHtml(offense[0])} 503// </div> 504// <div class="resultsContainer"> 505// <div class="results W">${offense[1].w}W</div> 506// <div class="results L">${offense[1].l}L</div> 507// </div> 508// <div class="metaResultsPercent"> 509// ${isNaN(percent) ? "No results" : percent + " %"} 510// </div> 511// </div> 512// </div> 513// ` 514 ` 515<div class="resultRow"> 516 <div class="imageRow"> 517 <div class="metaFightLookup"> 518 519 520 <a href="${"gw-meta.html?def=" + offense[0].split(",").map(x => heroesById[x]).join(",")}"> 521 <div class="metaFightIcons"> 522 ${imgHtml(offense[0])} 523 <div class="vSpace"></div> 524 </div> 525 </a> 526 </div> 527 <div class="resultsContainer"> 528 <div class="metaResults W"> 529 ${offense[1].l} 530 </div> 531 <img class="metaAtkImg" src="battle_pvp_icon_def.png"></img> 532 533 <div class="metaResults L"> 534 ${offense[1].w} 535 </div> 536 <img class="metaAtkImg" src="battle_pvp_icon_defeat.png"></img> 537 538 <div class="metaResultsPercent"> 539 ${isNaN(percent) ? "No results" : percent + " %"} 540 </div> 541 </div> 542 </div> 543</div> 544` 545 } 546 547 $('#buildDefResultRows').html(html) 548 $('#metaRows').html("") 549 } 550 }); 551} 552 553async function fetchCache(url) { 554 console.log("Fetching from url: " + url); 555 var myHeaders = new Headers(); 556 myHeaders.append('pragma', 'no-cache'); 557 myHeaders.append('cache-control', 'no-cache'); 558 559 const response = await fetch(url, { 560 method: 'GET', 561 headers: myHeaders, 562 mode: 'cors', // no-cors, *cors, same-origin 563 }); 564 const text = await response.text(); 565 const json = JSON.parse(text); 566 console.log("Finished fetching from url: " + url); 567 568 return json; 569} 570 571function sortByAttribute (arr, attributeStr) { 572 function compare(a, b) { 573 if (a[attributeStr] < b[attributeStr]) 574 return -1; 575 if (a[attributeStr] > b[attributeStr]) 576 return 1; 577 return 0; 578 } 579 580 return arr.sort(compare); 581} 582 583var questionCircle = "https://raw.githubusercontent.com/fribbels/Fribbels-Epic-7-Optimizer/main/data/cachedimages/question_circle.png"; 584function imgHtml(offenseStr) { 585 var heroIds = offenseStr.split(",") 586 var heroNames = heroIds.map(x => heroesById[x]) 587 var imgHtml = heroNames.map(x => { 588 return heroData[x] ? 589 `<img class="portrait" title="${x}" src=${heroData[x].assets.icon}></img>` : 590 `<img class="portrait" src=${questionCircle}></img>` 591 }) 592 593 return imgHtml.join(`<div class="vSpace"></div>`) 594}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.