feat: player evolution

This commit is contained in:
TresAbhi committed 2023-06-22 18:26:51 +00:00
1 parent e49bd6aa68
commit 91da65196d
10 files changed
+193 -71

No files matched your search

+2 -1
View File
@@ -7,4 +7,5 @@ package-lock.json
dist/
yarn-error.log
dist.zip
commands.txt
commands.txt
/test.*
+1 -1
View File
@@ -5,7 +5,7 @@
"main": "src/index.ts",
"type": "module",
"scripts": {
"dev": "yarn build:raw --dev && yarn start",
"dev": "yarn build:raw --dev && yarn start --dev",
"start": "node --enable-source-maps --trace-warnings dist/index.cjs",
"zip": "zip -r dist.zip dist",
"build:raw": "node scripts/build",
+23 -17
View File
@@ -12,6 +12,7 @@ import getWargamingResponse from '../core/blitz/getWargamingResponse.js';
import resolveTankId from '../core/blitz/resolveTankId.js';
import resolveTankName from '../core/blitz/resolveTankName.js';
import { tankopedia } from '../core/blitz/tankopedia.js';
import getPlayerHistories from '../core/blitzstars/getPlayerHistories.js';
import getTankHistories from '../core/blitzstars/getTankHistories.js';
import { supportBlitzStars } from '../core/interaction/supportBlitzStars.js';
import {
@@ -23,6 +24,7 @@ import getPeriodDataFromSubcommand from '../core/options/getPeriodDataFromSubcom
import { WARGAMING_APPLICATION_ID } from '../core/process/args.js';
import { CommandRegistry } from '../events/interactionCreate.js';
import { AccountInfo } from '../types/accountInfo.js';
import { Histories } from '../types/histories.js';
import { PlayerClanData } from '../types/playerClanData.js';
export default {
@@ -61,36 +63,40 @@ export default {
image = tankopedia[tankId].images.normal;
}
let histories: Histories;
const { evolutionName, start, end } =
getPeriodDataFromSubcommand(interaction);
let careerWinrate: Graph.Plot;
let careerBattles: Graph.Plot;
if (commandGroup === 'player') {
histories = await getPlayerHistories(server, id, {
start,
end,
includeLatestHistories: true,
includePreviousHistories: true,
});
} else {
const tankId = resolveTankId(tankIdRaw);
const histories = await getTankHistories(server, id, {
histories = await getTankHistories(server, id, {
tankId,
start,
end,
includeLatestHistories: true,
includePreviousHistories: true,
});
careerWinrate = histories.map(
(history) =>
[
history.last_battle_time,
(history.all.wins / history.all.battles) * 100,
] as Graph.PlotItem,
);
careerBattles = histories.map(
(history) =>
[history.last_battle_time, history.all.battles] as Graph.PlotItem,
);
}
const careerWinrate = histories.map(
(history) =>
[
history.last_battle_time,
(history.all.wins / history.all.battles) * 100,
] as Graph.PlotItem,
);
const careerBattles = histories.map(
(history) =>
[history.last_battle_time, history.all.battles] as Graph.PlotItem,
);
const winrateYs = careerWinrate.map(([, y]) => y);
const maxWinrate = Math.max(...winrateYs);
const minWinrate = Math.min(...winrateYs);
@@ -121,7 +127,7 @@ export default {
interaction.options.getSubcommand() as Period
]
}
xMaxLabel="Today"
xMaxLabel="Latest recorded battle"
leftVerticalMargin={{
min: minWinrate,
max: maxWinrate,
@@ -1,8 +1,6 @@
import { theme } from '../../../stitches.config.js';
import { Stat } from '../index.js';
import '../../../core/blitz/getWN8Percentile.js';
import PercentileIndicator from '../../PercentileIndicator.js';
import { Stat } from '../index.js';
export interface GenericStatsRowProps {
stat: Stat;
+92
View File
@@ -0,0 +1,92 @@
import { BlitzServer } from '../../constants/servers.js';
import { AccountInfo, AllStats } from '../../types/accountInfo.js';
import {
GetHistoriesOptions,
Histories,
History,
getHistoriesDefaultOptions,
} from '../../types/histories.js';
import getWargamingResponse from '../blitz/getWargamingResponse.js';
import { WARGAMING_APPLICATION_ID } from '../process/args.js';
import { emptyAllStats } from './getTankStatsOverTime.js';
export interface PlayerHistoryRaw {
clan: { clan_id: number; name: string; tag: string };
statistics: {
all: AllStats;
wn8: number;
wn7: number;
average_tier: number;
};
last_battle_time: number;
nickname: string;
account_id: number;
achievements: {
account_id: number;
[key: string]: number;
};
region: BlitzServer;
}
export type PlayerHistoriesRaw = PlayerHistoryRaw[];
export default async function getPlayerHistories(
server: BlitzServer,
id: number,
options?: Partial<GetHistoriesOptions>,
) {
const mergedOptions = { ...getHistoriesDefaultOptions, ...options };
const response = await fetch(
`https://www.blitzstars.com/api/playerstats/${id}`,
);
const playerHistories = ((await response.json()) as PlayerHistoriesRaw).map(
(history) =>
({
all: history.statistics.all,
last_battle_time: history.last_battle_time,
} satisfies History),
);
let lastNonCompliantIndex = -1;
let hasCompliedOnce = false;
const filtered = playerHistories.filter(({ last_battle_time }, index) => {
const complies =
last_battle_time >= mergedOptions.start &&
last_battle_time <= mergedOptions.end;
if (complies && !hasCompliedOnce) hasCompliedOnce = true;
if (!hasCompliedOnce) lastNonCompliantIndex = index;
return complies;
});
const latestNodes: Histories = [];
if (mergedOptions.includeLatestHistories) {
const {
last_battle_time,
statistics: { all },
} = (
await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
)
)[id];
latestNodes.push({ all, last_battle_time });
}
const previousNodes: Histories = [];
if (mergedOptions.includePreviousHistories) {
if (lastNonCompliantIndex !== -1) {
previousNodes.push(
playerHistories[lastNonCompliantIndex] ??
({
all: emptyAllStats,
last_battle_time: -Infinity,
} satisfies History),
);
}
}
return [...latestNodes, ...filtered, ...previousNodes] satisfies Histories;
}
+35 -32
View File
@@ -1,41 +1,34 @@
import { BlitzServer } from '../../constants/servers.js';
import { AllStats } from '../../types/accountInfo.js';
import {
GetHistoriesOptions,
Histories,
History,
getHistoriesDefaultOptions,
} from '../../types/histories.js';
import getTankStats from '../blitz/getTankStats.js';
import { emptyAllStats } from './getTankStatsOverTime.js';
export interface TankHistory {
all: AllStats;
last_battle_time: number;
mark_of_mastery: number;
battle_life_time: number;
account_id: number;
export interface TankHistory extends History {
tank_id: number;
}
export const emptyTankHistory: TankHistory = {
all: emptyAllStats,
last_battle_time: -Infinity,
mark_of_mastery: 0,
battle_life_time: 0,
account_id: 0,
tank_id: 0,
};
export type TankHistories = TankHistory[];
export interface GetTankHistoriesOptions {
start: number;
end: number;
includeLatestHistories: boolean;
includePreviousHistories: boolean;
export interface TankHistoryRaw extends TankHistory {
mark_of_mastery: number;
battle_life_time: number;
account_id: number;
}
export type TankHistoriesRaw = TankHistoryRaw[];
export interface GetTankHistoriesOptions extends GetHistoriesOptions {
tankId?: number;
}
export const getTankHistoriesDefaultOptions: GetTankHistoriesOptions = {
start: -Infinity,
end: Infinity,
includeLatestHistories: false,
includePreviousHistories: false,
...getHistoriesDefaultOptions,
tankId: undefined,
};
export default async function getTankHistories(
@@ -47,7 +40,16 @@ export default async function getTankHistories(
const tankHistoriesResponse = await fetch(
`https://www.blitzstars.com/api/tankhistories/for/${id}/`,
);
const tankHistories = (await tankHistoriesResponse.json()) as TankHistories;
const tankHistories = (
(await tankHistoriesResponse.json()) as TankHistoriesRaw
).map(
(history) =>
({
all: history.all,
last_battle_time: history.last_battle_time,
tank_id: history.tank_id,
} satisfies TankHistory),
);
const compliantTanks: number[] = [];
const lastBattles: Record<number, number> = {};
const lastNonCompliantIndex: Record<number, number> = {};
@@ -96,15 +98,16 @@ export default async function getTankHistories(
compliantTanks.forEach((tankId) => {
if (tankId in lastNonCompliantIndex) {
previousNodes.push(
tankHistories[lastNonCompliantIndex[tankId]] ?? {
...emptyTankHistory,
account_id: id,
tank_id: tankId,
},
tankHistories[lastNonCompliantIndex[tankId]] ??
({
all: emptyAllStats,
last_battle_time: -Infinity,
tank_id: tankId,
} satisfies TankHistory),
);
}
});
}
return [...previousNodes, ...filtered, ...latestNodes];
return [...latestNodes, ...filtered, ...previousNodes] satisfies Histories;
}
+4 -4
View File
@@ -1,8 +1,8 @@
import { BlitzServer } from '../../constants/servers.js';
import { AllStats } from '../../types/accountInfo.js';
import getTankHistories, {
TankHistories,
TankHistory,
TankHistoriesRaw,
TankHistoryRaw,
} from './getTankHistories.js';
export const emptyAllStats: AllStats = {
@@ -25,7 +25,7 @@ export const emptyAllStats: AllStats = {
wins: 0,
};
export const emptyTankHistoryNode: TankHistory = {
export const emptyTankHistoryNode: TankHistoryRaw = {
account_id: 0,
battle_life_time: 0,
last_battle_time: 0,
@@ -45,7 +45,7 @@ export default async function getTankStatsOverTime(
});
// sort them even though most won't be used
const tankSortedHistory: Record<number, TankHistories> = {};
const tankSortedHistory: Record<number, TankHistoriesRaw> = {};
const tankIds: number[] = [];
history.forEach((node) => {
if (!tankSortedHistory[node.tank_id]) {
+1 -1
View File
@@ -11,7 +11,7 @@ export const RELATIVE_PERIOD_NAMES: Record<Period, string> = {
'30': '30 days ago',
'60': '60 days ago',
'90': '90 days ago',
career: 'Start of career',
career: 'Extent of data',
custom: 'Custom period',
};
+10 -12
View File
@@ -124,20 +124,18 @@ try {
});
console.log(`Refreshing ${guildCommands.length} guild command(s).`);
Promise.all([
rest.put(
Routes.applicationGuildCommands(getClientId(), discord.sklld_guild_id),
{ body: guildCommands },
),
rest.put(
rest
.put(
Routes.applicationGuildCommands(getClientId(), discord.tres_guild_id),
{ body: guildCommands },
),
]).then((guildData) => {
console.log(
`Successfully refreshed ${guildData.flat().length / 2} guild command(s).`,
);
});
)
.then((guildData) => {
console.log(
`Successfully refreshed ${
(guildData as unknown[]).length
} guild command(s).`,
);
});
} catch (error) {
console.error(error);
}
+24
View File
@@ -0,0 +1,24 @@
// a cleaner simplification of Blitz Stars' histories because it's a mess there
import { AllStats } from './accountInfo.js';
export interface History {
all: AllStats;
last_battle_time: number;
}
export type Histories = History[];
export interface GetHistoriesOptions {
start: number;
end: number;
includeLatestHistories: boolean;
includePreviousHistories: boolean;
}
export const getHistoriesDefaultOptions: GetHistoriesOptions = {
start: -Infinity,
end: Infinity,
includeLatestHistories: false,
includePreviousHistories: false,
};