Files
buildpath/match_collector/champion_stat.ts
2024-11-27 16:14:14 +01:00

236 lines
8.4 KiB
TypeScript

function sameArrays(array1 : Array<any>, array2 : Array<any>) {
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<number>
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<Rune> = [];
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<number> = []
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<number> = []
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}