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