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 } function arrayRemovePercentage(array: Array<{count:number}>, totalGames:number, percentage: number) { let toRemove : Array<{count:number}> = [] for(let item of array) { if((item.count/totalGames) < 0.05) { toRemove.push(item) } } for(let tr of toRemove) { array.splice(array.indexOf(tr), 1) } } 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}> } type Champion = { id: Number name: String alias: String } async function championInfos(client, patch: number, champion: Champion) { const championId = champion.id 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 if(items.length >= 1) { 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; } } let totalChampionMatches = winningMatches + losingMatches; // Filter runes to keep 3 most played runes.sort((a, b) => b.count - a.count) if(runes.length > 3) runes.splice(3, runes.length - 3) // Compute runes pickrate for(let rune of runes) rune.pickrate = rune.count / totalChampionMatches; // Cut item tree branches to keep only 4 branches every time and with percentage threshold builds.tree.count = totalChampionMatches; treeCutBranches(builds.tree, 4, 0.05) treeSort(builds.tree) // Cut item start, to only 4 and with percentage threshold arrayRemovePercentage(builds.start, totalChampionMatches, 0.05) builds.start.sort((a, b) => b.count - a.count) if(builds.start.length > 4) builds.start.splice(4, builds.start.length - 4) // Remove boots that are not within percentage threshold arrayRemovePercentage(builds.boots, totalChampionMatches, 0.05) builds.boots.sort((a, b) => b.count - a.count) builds.bootsFirst /= (winningMatches + losingMatches) builds.lateGame.sort((a, b) => b.count - a.count) return {name: champion.name, alias: champion.alias.toLowerCase(), id: championId, winrate: winningMatches / totalChampionMatches, gameCount: totalChampionMatches, pickrate: totalChampionMatches/totalMatches, runes: runes, builds: builds }; } async function makeChampionStat(client, patch : number, champion : Champion) { const championInfo = await championInfos(client, patch, champion) 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 : number) { 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) i += 1 } const database = client.db("champions") const collection = database.collection(patch) await collection.createIndex({id:1}) await collection.createIndex({alias:1}) } export default {makeChampionsStats}