/** * This script is used to merge multiple stats_playerDrop files into a single file with the last 336 hours of data. * NOTE: use npx tsx scripts/dev/makeOldStatsFile.local.ts to run this script */ import fs from 'node:fs'; import { shuffle } from 'd3-array'; import { PDLChangeEventType, PDLFileType } from '../../core/modules/Metrics/playerDrop/playerDropSchemas'; import { MultipleCounter } from '../../core/modules/Metrics/statsUtils'; type CountersType = { drops: MultipleCounter, resKicks: MultipleCounter, crashes: MultipleCounter, changes: PDLChangeEventType[], }; const getRandomResource = () => { const pool = [ 'anticheat', 'anticheat', 'anticheat', 'anticheat', 'txAdmin', 'txAdmin', 'txAdmin', 'vMenu', 'es_extended', ]; return pool[Math.floor(Math.random() * pool.length)]; } export default (sourceFiles: string[], targetFileName: string) => { let logCounters: CountersType[] = []; for (const fileName of sourceFiles) { const fileData = JSON.parse(fs.readFileSync(fileName, 'utf8')) as PDLFileType; console.log('fileData.log.length:', fileData.log.length); for (const log of fileData.log) { const resKicks = new MultipleCounter(); const drops = log.dropTypes.map(([type, count]): [string, number] | false => { if (type === 'user-initiated') { return ['player', count] } else if (type === 'server-initiated') { resKicks.count('txAdmin', count); return ['resource', count]; } else if (type === 'unknown') { if (Math.random() <= 0.2) { return [type, count]; } else { resKicks.count(getRandomResource(), count); return ['resource', count]; } } else { return [type, count]; } }).filter((x): x is [string, number] => Array.isArray(x)); logCounters.push({ // drops: new MultipleCounter(log.dropTypes.filter(([type]) => type !== 'server-initiated')), drops: new MultipleCounter(drops), resKicks: resKicks, crashes: new MultipleCounter(log.crashTypes), changes: log.changes, }); } } console.log('Logs Hours:', logCounters.length); //Shuffle and merge all crashes into 336 MultipleCounters and shuffle them again logCounters = shuffle(logCounters); let mergedCounters: CountersType[] = []; for (let i = 0; i < logCounters.length; i++) { const writeIndex = i % 336; const currCounters = logCounters[i]; if (!mergedCounters[writeIndex]) { mergedCounters[writeIndex] = currCounters; } else { mergedCounters[writeIndex].drops.merge(currCounters.drops); mergedCounters[writeIndex].resKicks.merge(currCounters.resKicks); mergedCounters[writeIndex].crashes.merge(currCounters.crashes); mergedCounters[writeIndex].changes.push(...currCounters.changes); } } mergedCounters = shuffle(mergedCounters); console.log('mergedCrashes.length:', mergedCounters.length); //Prepare new hours array const last336Hours: Date[] = []; const currentDate = new Date(); currentDate.setMinutes(0, 0, 0); for (let i = 335; i >= 0; i--) { const hour = new Date(currentDate.getTime() - i * 60 * 60 * 1000); last336Hours.push(hour); } console.log('last336Hours.length:', last336Hours.length); //Prepare new log with the data const newLog: PDLFileType['log'] = []; for (const hour of last336Hours) { const counters = mergedCounters.pop()!; const newHour = { hour: hour.toISOString(), changes: counters?.changes ?? [], dropTypes: counters?.drops.toArray() ?? [], resKicks: counters?.resKicks.toArray() ?? [], crashTypes: counters?.crashes.toArray() ?? [], }; newLog.push(newHour); console.dir({ hour: newHour.hour, changes: newHour.changes.length, drops: counters.drops.sum(), resKicks: counters.resKicks.sum(), crashes: counters.crashes.sum(), }); } //Write new file const newStatsFile: PDLFileType = { version: 2, lastGameVersion: 'unknown', lastServerVersion: 'unknown', lastResourceList: [], lastUnknownReasons: [], log: newLog, } fs.writeFileSync(targetFileName, JSON.stringify(newStatsFile)); }