function sameArrays(array1 : Array, array2 : Array) { if(array1.length != array2.length) return false; for(let e of array1) { if(!array2.includes(e)) return false; } return true; } import { ItemTree, treeInit, treeMerge, treeCutBranches, treeSort } from "./item_tree"; const itemDict = new Map() async function itemList() { const response = await fetch("https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/items.json") const list = await response.json() return list } type Rune = { count: number primaryStyle: number secondaryStyle: number selections: Array pickrate?: number }; type Builds = { tree: ItemTree start: Array<{data: number, count: number}> bootsFirst: number boots: Array<{data: number, count: number}> lateGame: Array<{data: number, count: number}> } async function championInfos(client, patch: number, championId: number) { const database = client.db("matches"); const matches = database.collection(patch) const allMatches = matches.find() let winningMatches = 0; let losingMatches = 0; let totalMatches = 0; const runes : Array = []; const builds : Builds = {tree:treeInit(), start: [], bootsFirst: 0, boots: [], lateGame: []} for await (let match of allMatches) { totalMatches += 1; let participantIndex = 0; for(let participant of match.info.participants) { participantIndex += 1 if(participant.championId != championId) continue; // Winrate if(participant.win) winningMatches += 1; else losingMatches += 1; // Runes const primaryStyle = participant.perks.styles[0].style const secondaryStyle = participant.perks.styles[1].style const selections : Array = [] for(let style of participant.perks.styles) { for(let perk of style.selections) { selections.push(perk.perk) } } const gameRunes : Rune = {count:1, primaryStyle: primaryStyle, secondaryStyle: secondaryStyle, selections: selections}; let addRunes = true; for(let rune of runes) { if(rune.primaryStyle == gameRunes.primaryStyle && rune.secondaryStyle == gameRunes.secondaryStyle && sameArrays(rune.selections, gameRunes.selections)) { rune.count++; addRunes = false; break; } } if(addRunes) runes.push(gameRunes) // Items const items : Array = [] for(let frame of match.timeline.info.frames) { for(let event of frame.events) { if(event.participantId != participantIndex) continue; if(event.type == "ITEM_UNDO") { if(items.length > 0 && items[items.length - 1] == event.beforeId) { items.pop() } continue; } if(event.type != "ITEM_PURCHASED") continue; let itemInfo = itemDict.get(event.itemId) // Handle boots differently if(itemInfo.categories.includes("Boots")){ if(itemInfo.to.length == 0 || event.itemId == 3006) { // Check for bootsFirst if(items.length < 2) { builds.bootsFirst += 1 } // Add to boots const already = builds.boots.find((x) => x.data == event.itemId) if(already == undefined) builds.boots.push({count:1, data:event.itemId}) else already.count += 1 } continue; } // Check if item should be included if(itemInfo.categories.includes("Consumable")) continue; if(itemInfo.categories.includes("Trinket")) continue; // Ignore Cull as not-first item if(event.itemId == 1083 && items.length >= 1) continue; // Ignore non-final items, except when first item bought if(itemInfo.to.length != 0 && items.length >= 1) continue; items.push(event.itemId) } } // Core items treeMerge(builds.tree, items.slice(1, 4)) // Start items const already = builds.start.find((x) => x.data == items[0]) if(already == undefined) builds.start.push({count:1, data:items[0]}) else already.count += 1 // Late game items for(let item of items.slice(4)) { const already = builds.lateGame.find((x) => x.data == item) if(already == undefined) builds.lateGame.push({count:1, data:item}) else already.count += 1 } break; } } // Filter runes to keep 3 most played const maxes = [0, 0, 0] // 24, 2, 1 -> 18 > 1 -> 24, 2, 2 -> 18 > 2 -> 24, 24, 2 const maxRunes : Array = [null, null, null] for(let rune of runes) { let maxcount = 2; if(rune.count <= maxes[maxcount]) continue; while(maxcount > 0 && rune.count > maxes[maxcount]) { maxes[maxcount] = maxes[maxcount - 1]; maxRunes[maxcount] = maxRunes[maxcount - 1]; maxcount--; } rune.pickrate = rune.count / (winningMatches + losingMatches) if(rune.count <= maxes[maxcount]) maxcount++; maxes[maxcount] = rune.count maxRunes[maxcount] = rune } // Cut item tree branches to keep only 4 branches every time and with percentage threshold builds.tree.count = (winningMatches + losingMatches) treeCutBranches(builds.tree, 4, 0.05) treeSort(builds.tree) // Cut item start, to only 4 and with percentage threshold while(builds.start.length > 4) { let leastUsedItem = builds.start.reduce((a, b) => Math.min(a.count, b.count) == a.count ? a : b, {data:undefined, count: +Infinity}) builds.start.splice(builds.start.indexOf(leastUsedItem), 1) } let toRemove : Array<{data: number, count:number}> = [] for(let item of builds.start) { if((item.count/(winningMatches + losingMatches)) < 0.05) { toRemove.push(item) } } for(let tr of toRemove) { builds.start.splice(builds.start.indexOf(tr), 1) } builds.start.sort((a, b) => b.count - a.count) builds.boots.sort((a, b) => b.count - a.count) builds.bootsFirst /= (winningMatches + losingMatches) builds.lateGame.sort((a, b) => b.count - a.count) return {id: championId, winrate:winningMatches / (winningMatches + losingMatches), gameCount:(winningMatches + losingMatches), pickrate:(winningMatches + losingMatches)/totalMatches, runes: maxRunes.filter((x) => x != null), builds: builds }; } async function makeChampionStat(client, patch, championId) { const championInfo = await championInfos(client, patch, championId) const database = client.db("champions") const collection = database.collection(patch) await collection.updateOne({id: championInfo.id}, {$set: championInfo}, { upsert: true }) } async function championList() { const response = await fetch("https://raw.communitydragon.org/latest/plugins/rcp-be-lol-game-data/global/default/v1/champion-summary.json"); const list = await response.json() return list.slice(1) } async function makeChampionsStats(client, patch) { var globalItems = await itemList() for(let item of globalItems) { itemDict.set(item.id, item) } const list = await championList() console.log("Generating stats for " + list.length + " champions") let i = 0; for(let champion of list) { console.log("Entry " + i + "/" + list.length + " (" + champion.name + ")...") await makeChampionStat(client, patch, champion.id) i += 1 } const database = client.db("champions") const collection = database.collection(patch) await collection.createIndex({id:1}) } export default {makeChampionsStats}